mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-15 20:25:57 +08:00
Merge branch 'master' of https://github.com/prusa3d/PrusaSlicer into et_trafo_matrix
This commit is contained in:
commit
7cca58a56d
2
deps/wxWidgets/wxWidgets.cmake
vendored
2
deps/wxWidgets/wxWidgets.cmake
vendored
@ -10,7 +10,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
endif()
|
||||
|
||||
set(_unicode_utf8 OFF)
|
||||
if (UNIX) # wxWidgets will not use char as the underlying type for wxString unless its forced to.
|
||||
if (UNIX AND NOT APPLE) # wxWidgets will not use char as the underlying type for wxString unless its forced to.
|
||||
set (_unicode_utf8 ON)
|
||||
endif()
|
||||
|
||||
|
@ -15,20 +15,4 @@ sub config {
|
||||
return $self->object->config;
|
||||
}
|
||||
|
||||
sub region {
|
||||
my $self = shift;
|
||||
my ($region_id) = @_;
|
||||
|
||||
while ($self->region_count <= $region_id) {
|
||||
$self->add_region($self->object->print->get_region($self->region_count));
|
||||
}
|
||||
|
||||
return $self->get_region($region_id);
|
||||
}
|
||||
|
||||
sub regions {
|
||||
my ($self) = @_;
|
||||
return [ map $self->get_region($_), 0..($self->region_count-1) ];
|
||||
}
|
||||
|
||||
1;
|
||||
|
@ -17,15 +17,20 @@ src/slic3r/GUI/GalleryDialog.cpp
|
||||
src/slic3r/GUI/GCodeViewer.cpp
|
||||
src/slic3r/GUI/GLCanvas3D.cpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoCut.cpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoCut.hpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoMove.cpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoScale.cpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp
|
||||
|
@ -1,2 +1,4 @@
|
||||
min_slic3r_version = 2.4.2
|
||||
1.0.1 Added 350mm Voron v1 variant. Updated max print heights. Removed redundant v1 volcano nozzle variants.
|
||||
min_slic3r_version = 2.4.0-beta0
|
||||
1.0.0 Initial version
|
||||
|
@ -7,7 +7,7 @@
|
||||
name = Voron
|
||||
# Configuration version of this file. Config file will only be installed, if the config_version differs.
|
||||
# This means, the server may force the PrusaSlicer configuration to be downgraded.
|
||||
config_version = 1.0.0
|
||||
config_version = 1.0.1
|
||||
# Where to get the updates from?
|
||||
config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Voron/
|
||||
|
||||
@ -72,21 +72,30 @@ default_materials = Basic PLA @VORON; Basic PLA VOLCANO @VORON; Basic PET @VORON
|
||||
|
||||
[printer_model:Voron_v1_250_afterburner]
|
||||
name = Voron v1 250mm3
|
||||
variants = 0.4; 0.25; 0.3; 0.5; 0.6; 0.8; volcano 0.6; volcano 0.8; volcano 1.0; volcano 1.2
|
||||
variants = 0.4; 0.25; 0.3; 0.5; 0.6; 0.8
|
||||
technology = FFF
|
||||
family = Voron v1 Afterburner
|
||||
bed_model = printbed-v1-250.stl
|
||||
bed_texture = bedtexture-v1-250.png
|
||||
default_materials = Basic PLA @VORON; Basic PLA VOLCANO @VORON; Basic PET @VORON; Basic PET VOLCANO @VORON; Basic ABS @VORON; Basic ABS VOLCANO @VORON
|
||||
default_materials = Basic PLA @VORON; Basic PET @VORON; Basic ABS @VORON
|
||||
|
||||
[printer_model:Voron_v1_300_afterburner]
|
||||
name = Voron v1 300mm3
|
||||
variants = 0.4; 0.25; 0.3; 0.5; 0.6; 0.8; volcano 0.6; volcano 0.8; volcano 1.0; volcano 1.2
|
||||
variants = 0.4; 0.25; 0.3; 0.5; 0.6; 0.8
|
||||
technology = FFF
|
||||
family = Voron v1 Afterburner
|
||||
bed_model = printbed-v1-300.stl
|
||||
bed_texture = bedtexture-v1-300.png
|
||||
default_materials = Basic PLA @VORON; Basic PLA VOLCANO @VORON; Basic PET @VORON; Basic PET VOLCANO @VORON; Basic ABS @VORON; Basic ABS VOLCANO @VORON
|
||||
default_materials = Basic PLA @VORON; Basic PET @VORON; Basic ABS @VORON
|
||||
|
||||
[printer_model:Voron_v1_350_afterburner]
|
||||
name = Voron v1 350mm3
|
||||
variants = 0.4; 0.25; 0.3; 0.5; 0.6; 0.8
|
||||
technology = FFF
|
||||
family = Voron v1 Afterburner
|
||||
bed_model = printbed-v1-350.stl
|
||||
bed_texture = bedtexture-v2-350.png
|
||||
default_materials = Basic PLA @VORON; Basic PET @VORON; Basic ABS @VORON
|
||||
|
||||
[printer_model:Voron_v0_120]
|
||||
name = Voron Zero 120mm3
|
||||
@ -239,21 +248,21 @@ retract_speed = 50
|
||||
[printer:*Voron_v2_250*]
|
||||
inherits = *common*
|
||||
bed_shape = 0x0,250x0,250x250,0x250
|
||||
max_print_height = 250
|
||||
max_print_height = 230
|
||||
printer_model = Voron_v2_250
|
||||
printer_notes = Unoffical profile.\nPRINTER_HAS_BOWDEN\nE3DV6
|
||||
|
||||
[printer:*Voron_v2_300*]
|
||||
inherits = *common*
|
||||
bed_shape = 0x0,300x0,300x300,0x300
|
||||
max_print_height = 300
|
||||
max_print_height = 280
|
||||
printer_model = Voron_v2_300
|
||||
printer_notes = Unoffical profile.\nPRINTER_HAS_BOWDEN\nE3DV6
|
||||
|
||||
[printer:*Voron_v2_350*]
|
||||
inherits = *common*
|
||||
bed_shape = 0x0,350x0,350x350,0x350
|
||||
max_print_height = 350
|
||||
max_print_height = 330
|
||||
printer_model = Voron_v2_350
|
||||
printer_notes = Unoffical profile.\nPRINTER_HAS_BOWDEN\nE3DV6
|
||||
|
||||
@ -282,10 +291,17 @@ printer_notes = Unoffical profile.\nE3DV6
|
||||
[printer:*Voron_v1_300_afterburner*]
|
||||
inherits = *common*; *afterburner*
|
||||
bed_shape = 0x0,300x0,300x300,0x300
|
||||
max_print_height = 230
|
||||
max_print_height = 280
|
||||
printer_model = Voron_v1_300_afterburner
|
||||
printer_notes = Unoffical profile.\nE3DV6
|
||||
|
||||
[printer:*Voron_v1_350_afterburner*]
|
||||
inherits = *common*; *afterburner*
|
||||
bed_shape = 0x0,350x0,350x350,0x350
|
||||
max_print_height = 330
|
||||
printer_model = Voron_v1_350_afterburner
|
||||
printer_notes = Unoffical profile.\nE3DV6
|
||||
|
||||
[printer:*Voron_v0_120*]
|
||||
inherits = *common*
|
||||
bed_shape = 0x0,120x0,120x120,0x120
|
||||
@ -455,6 +471,24 @@ inherits = *Voron_v1_300_afterburner*; *0.6nozzle*
|
||||
[printer:Voron_v1_300_afterburner 0.8 nozzle]
|
||||
inherits = *Voron_v1_300_afterburner*; *0.8nozzle*
|
||||
|
||||
[printer:Voron_v1_350_afterburner 0.25 nozzle]
|
||||
inherits = *Voron_v1_350_afterburner*; *0.25nozzle*
|
||||
|
||||
[printer:Voron_v1_350_afterburner 0.3 nozzle]
|
||||
inherits = *Voron_v1_350_afterburner*; *0.3nozzle*
|
||||
|
||||
[printer:Voron_v1_350_afterburner 0.4 nozzle]
|
||||
inherits = *Voron_v1_350_afterburner*; *0.4nozzle*
|
||||
|
||||
[printer:Voron_v1_350_afterburner 0.5 nozzle]
|
||||
inherits = *Voron_v1_350_afterburner*; *0.5nozzle*
|
||||
|
||||
[printer:Voron_v1_350_afterburner 0.6 nozzle]
|
||||
inherits = *Voron_v1_350_afterburner*; *0.6nozzle*
|
||||
|
||||
[printer:Voron_v1_350_afterburner 0.8 nozzle]
|
||||
inherits = *Voron_v1_350_afterburner*; *0.8nozzle*
|
||||
|
||||
[printer:Voron_v2_250_afterburner 0.25 nozzle]
|
||||
inherits = *Voron_v2_250_afterburner*; *0.25nozzle*
|
||||
|
||||
@ -652,7 +686,7 @@ fill_angle = 45
|
||||
fill_density = 15%
|
||||
fill_pattern = gyroid
|
||||
first_layer_acceleration = 1000
|
||||
first_layer_height = 75%
|
||||
first_layer_height = 0.2
|
||||
first_layer_speed = 30
|
||||
gap_fill_speed = 40
|
||||
gcode_comments = 0
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 39 KiB |
Binary file not shown.
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 39 KiB |
Binary file not shown.
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 39 KiB |
@ -837,6 +837,9 @@ extern "C" {
|
||||
"leak:libnvidia-tls.so\n" // For NVidia driver.
|
||||
"leak:terminator_CreateDevice\n" // For Intel Vulkan drivers.
|
||||
"leak:swrast_dri.so\n" // For Mesa 3D software driver.
|
||||
"leak:amdgpu_dri.so\n" // For AMD driver.
|
||||
"leak:libdrm_amdgpu.so\n" // For AMD driver.
|
||||
"leak:libdbus-1.so\n" // For D-Bus library. Unsure if it is a leak or not.
|
||||
;
|
||||
}
|
||||
}
|
||||
|
@ -771,8 +771,8 @@ inline bool is_any_triangle_in_radius(
|
||||
auto distancer = detail::IndexedTriangleSetDistancer<VertexType, IndexedFaceType, TreeType, VectorType>
|
||||
{ vertices, faces, tree, point };
|
||||
|
||||
size_t hit_idx;
|
||||
VectorType hit_point = VectorType::Ones() * (std::nan(""));
|
||||
size_t hit_idx;
|
||||
VectorType hit_point = VectorType::Ones() * (NaN<typename VectorType::Scalar>);
|
||||
|
||||
if(tree.empty())
|
||||
{
|
||||
@ -828,22 +828,22 @@ struct Intersecting<Eigen::AlignedBox<CoordType, NumD>> {
|
||||
|
||||
template<class G> auto intersecting(const G &g) { return Intersecting<G>{g}; }
|
||||
|
||||
template<class G> struct Containing {};
|
||||
template<class G> struct Within {};
|
||||
|
||||
// Intersection predicate specialization for box-box intersections
|
||||
template<class CoordType, int NumD>
|
||||
struct Containing<Eigen::AlignedBox<CoordType, NumD>> {
|
||||
struct Within<Eigen::AlignedBox<CoordType, NumD>> {
|
||||
Eigen::AlignedBox<CoordType, NumD> box;
|
||||
|
||||
Containing(const Eigen::AlignedBox<CoordType, NumD> &bb): box{bb} {}
|
||||
Within(const Eigen::AlignedBox<CoordType, NumD> &bb): box{bb} {}
|
||||
|
||||
bool operator() (const typename Tree<NumD, CoordType>::Node &node) const
|
||||
{
|
||||
return box.contains(node.bbox);
|
||||
return node.is_leaf() ? box.contains(node.bbox) : box.intersects(node.bbox);
|
||||
}
|
||||
};
|
||||
|
||||
template<class G> auto containing(const G &g) { return Containing<G>{g}; }
|
||||
template<class G> auto within(const G &g) { return Within<G>{g}; }
|
||||
|
||||
namespace detail {
|
||||
|
||||
@ -858,7 +858,7 @@ void traverse_recurse(const Tree<Dims, T> &tree,
|
||||
if (!pred(tree.node(idx))) return;
|
||||
|
||||
if (tree.node(idx).is_leaf()) {
|
||||
callback(tree.node(idx).idx);
|
||||
callback(tree.node(idx));
|
||||
} else {
|
||||
|
||||
// call this with left and right node idx:
|
||||
|
@ -15,7 +15,7 @@ class CircleBed {
|
||||
double radius_;
|
||||
public:
|
||||
|
||||
inline CircleBed(): center_(0, 0), radius_(std::nan("")) {}
|
||||
inline CircleBed(): center_(0, 0), radius_(NaNd) {}
|
||||
explicit inline CircleBed(const Point& c, double r): center_(c), radius_(r) {}
|
||||
|
||||
inline double radius() const { return radius_; }
|
||||
|
@ -212,6 +212,7 @@ set(SLIC3R_SOURCES
|
||||
PrintObject.cpp
|
||||
PrintObjectSlice.cpp
|
||||
PrintRegion.cpp
|
||||
PointGrid.hpp
|
||||
PNGReadWrite.hpp
|
||||
PNGReadWrite.cpp
|
||||
QuadricEdgeCollapse.cpp
|
||||
|
@ -402,6 +402,42 @@ std::ostream& ConfigDef::print_cli_help(std::ostream& out, bool show_defaults, s
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string ConfigBase::SetDeserializeItem::format(std::initializer_list<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
|
||||
|
@ -1950,6 +1950,11 @@ public:
|
||||
throw BadOptionTypeException("Conversion to a wrong type");
|
||||
return static_cast<TYPE*>(opt);
|
||||
}
|
||||
|
||||
template<class T> T* opt(const t_config_option_key &opt_key, bool create = false)
|
||||
{ return dynamic_cast<T*>(this->optptr(opt_key, create)); }
|
||||
template<class T> const T* opt(const t_config_option_key &opt_key) const
|
||||
{ return dynamic_cast<const T*>(this->optptr(opt_key)); }
|
||||
|
||||
// Apply all keys of other ConfigBase defined by this->def() to this ConfigBase.
|
||||
// An UnknownOptionException is thrown in case some option keys of other are not defined by this->def(),
|
||||
@ -1995,11 +2000,23 @@ public:
|
||||
SetDeserializeItem(const std::string &opt_key, const bool value, bool append = false) : opt_key(opt_key), opt_value(value ? "1" : "0"), append(append) {}
|
||||
SetDeserializeItem(const char *opt_key, const int value, bool append = false) : opt_key(opt_key), opt_value(std::to_string(value)), append(append) {}
|
||||
SetDeserializeItem(const std::string &opt_key, const int value, bool append = false) : opt_key(opt_key), opt_value(std::to_string(value)), append(append) {}
|
||||
SetDeserializeItem(const char *opt_key, const std::initializer_list<int> values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {}
|
||||
SetDeserializeItem(const std::string &opt_key, const std::initializer_list<int> values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {}
|
||||
SetDeserializeItem(const char *opt_key, const float value, bool append = false) : opt_key(opt_key), opt_value(float_to_string_decimal_point(value)), append(append) {}
|
||||
SetDeserializeItem(const std::string &opt_key, const float value, bool append = false) : opt_key(opt_key), opt_value(float_to_string_decimal_point(value)), append(append) {}
|
||||
SetDeserializeItem(const char *opt_key, const double value, bool append = false) : opt_key(opt_key), opt_value(float_to_string_decimal_point(value)), append(append) {}
|
||||
SetDeserializeItem(const std::string &opt_key, const double value, bool append = false) : opt_key(opt_key), opt_value(float_to_string_decimal_point(value)), append(append) {}
|
||||
SetDeserializeItem(const char *opt_key, const std::initializer_list<float> values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {}
|
||||
SetDeserializeItem(const std::string &opt_key, const std::initializer_list<float> values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {}
|
||||
SetDeserializeItem(const char *opt_key, const std::initializer_list<double> values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {}
|
||||
SetDeserializeItem(const std::string &opt_key, const std::initializer_list<double> values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {}
|
||||
|
||||
std::string opt_key; std::string opt_value; bool append = false;
|
||||
|
||||
private:
|
||||
static std::string format(std::initializer_list<int> values);
|
||||
static std::string format(std::initializer_list<float> values);
|
||||
static std::string format(std::initializer_list<double> values);
|
||||
};
|
||||
// May throw BadOptionTypeException() if the operation fails.
|
||||
void set_deserialize(std::initializer_list<SetDeserializeItem> items, ConfigSubstitutionContext& substitutions);
|
||||
@ -2008,7 +2025,31 @@ public:
|
||||
|
||||
double get_abs_value(const t_config_option_key &opt_key) const;
|
||||
double get_abs_value(const t_config_option_key &opt_key, double ratio_over) const;
|
||||
void setenv_() const;
|
||||
|
||||
std::string& opt_string(const t_config_option_key &opt_key, bool create = false) { return this->option<ConfigOptionString>(opt_key, create)->value; }
|
||||
const std::string& opt_string(const t_config_option_key &opt_key) const { return const_cast<ConfigBase*>(this)->opt_string(opt_key); }
|
||||
std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionStrings>(opt_key)->get_at(idx); }
|
||||
const std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) const { return const_cast<ConfigBase*>(this)->opt_string(opt_key, idx); }
|
||||
|
||||
double& opt_float(const t_config_option_key &opt_key) { return this->option<ConfigOptionFloat>(opt_key)->value; }
|
||||
const double& opt_float(const t_config_option_key &opt_key) const { return dynamic_cast<const ConfigOptionFloat*>(this->option(opt_key))->value; }
|
||||
double& opt_float(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionFloats>(opt_key)->get_at(idx); }
|
||||
const double& opt_float(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionFloats*>(this->option(opt_key))->get_at(idx); }
|
||||
|
||||
int& opt_int(const t_config_option_key &opt_key) { return this->option<ConfigOptionInt>(opt_key)->value; }
|
||||
int opt_int(const t_config_option_key &opt_key) const { return dynamic_cast<const ConfigOptionInt*>(this->option(opt_key))->value; }
|
||||
int& opt_int(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionInts>(opt_key)->get_at(idx); }
|
||||
int opt_int(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionInts*>(this->option(opt_key))->get_at(idx); }
|
||||
|
||||
// In ConfigManipulation::toggle_print_fff_options, it is called on option with type ConfigOptionEnumGeneric* and also ConfigOptionEnum*.
|
||||
// Thus the virtual method getInt() is used to retrieve the enum value.
|
||||
template<typename ENUM>
|
||||
ENUM opt_enum(const t_config_option_key &opt_key) const { return static_cast<ENUM>(this->option(opt_key)->getInt()); }
|
||||
|
||||
bool opt_bool(const t_config_option_key &opt_key) const { return this->option<ConfigOptionBool>(opt_key)->value != 0; }
|
||||
bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { return this->option<ConfigOptionBools>(opt_key)->get_at(idx) != 0; }
|
||||
|
||||
void setenv_() const;
|
||||
ConfigSubstitutions load(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule);
|
||||
ConfigSubstitutions load_from_ini(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule);
|
||||
ConfigSubstitutions load_from_ini_string(const std::string &data, ForwardCompatibilitySubstitutionRule compatibility_rule);
|
||||
@ -2017,10 +2058,10 @@ public:
|
||||
ConfigSubstitutions load_from_ini_string_commented(std::string &&data, ForwardCompatibilitySubstitutionRule compatibility_rule);
|
||||
ConfigSubstitutions load_from_gcode_file(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule);
|
||||
ConfigSubstitutions load(const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule);
|
||||
void save(const std::string &file) const;
|
||||
void save(const std::string &file) const;
|
||||
|
||||
// Set all the nullable values to nils.
|
||||
void null_nullables();
|
||||
void null_nullables();
|
||||
|
||||
static size_t load_from_gcode_string_legacy(ConfigBase& config, const char* str, ConfigSubstitutionContext& substitutions);
|
||||
|
||||
@ -2129,10 +2170,6 @@ public:
|
||||
// Allow DynamicConfig to be instantiated on ints own without a definition.
|
||||
// If the definition is not defined, the method requiring the definition will throw NoDefinitionException.
|
||||
const ConfigDef* def() const override { return nullptr; }
|
||||
template<class T> T* opt(const t_config_option_key &opt_key, bool create = false)
|
||||
{ return dynamic_cast<T*>(this->option(opt_key, create)); }
|
||||
template<class T> const T* opt(const t_config_option_key &opt_key) const
|
||||
{ return dynamic_cast<const T*>(this->option(opt_key)); }
|
||||
// Overrides ConfigResolver::optptr().
|
||||
const ConfigOption* optptr(const t_config_option_key &opt_key) const override;
|
||||
// Overrides ConfigBase::optptr(). Find ando/or create a ConfigOption instance for a given name.
|
||||
@ -2163,29 +2200,6 @@ public:
|
||||
// Returns options being equal in the two configs, ignoring options not present in both configs.
|
||||
t_config_option_keys equal(const DynamicConfig &other) const;
|
||||
|
||||
std::string& opt_string(const t_config_option_key &opt_key, bool create = false) { return this->option<ConfigOptionString>(opt_key, create)->value; }
|
||||
const std::string& opt_string(const t_config_option_key &opt_key) const { return const_cast<DynamicConfig*>(this)->opt_string(opt_key); }
|
||||
std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionStrings>(opt_key)->get_at(idx); }
|
||||
const std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) const { return const_cast<DynamicConfig*>(this)->opt_string(opt_key, idx); }
|
||||
|
||||
double& opt_float(const t_config_option_key &opt_key) { return this->option<ConfigOptionFloat>(opt_key)->value; }
|
||||
const double& opt_float(const t_config_option_key &opt_key) const { return dynamic_cast<const ConfigOptionFloat*>(this->option(opt_key))->value; }
|
||||
double& opt_float(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionFloats>(opt_key)->get_at(idx); }
|
||||
const double& opt_float(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionFloats*>(this->option(opt_key))->get_at(idx); }
|
||||
|
||||
int& opt_int(const t_config_option_key &opt_key) { return this->option<ConfigOptionInt>(opt_key)->value; }
|
||||
int opt_int(const t_config_option_key &opt_key) const { return dynamic_cast<const ConfigOptionInt*>(this->option(opt_key))->value; }
|
||||
int& opt_int(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionInts>(opt_key)->get_at(idx); }
|
||||
int opt_int(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionInts*>(this->option(opt_key))->get_at(idx); }
|
||||
|
||||
// In ConfigManipulation::toggle_print_fff_options, it is called on option with type ConfigOptionEnumGeneric* and also ConfigOptionEnum*.
|
||||
// Thus the virtual method getInt() is used to retrieve the enum value.
|
||||
template<typename ENUM>
|
||||
ENUM opt_enum(const t_config_option_key &opt_key) const { return static_cast<ENUM>(this->option(opt_key)->getInt()); }
|
||||
|
||||
bool opt_bool(const t_config_option_key &opt_key) const { return this->option<ConfigOptionBool>(opt_key)->value != 0; }
|
||||
bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { return this->option<ConfigOptionBools>(opt_key)->get_at(idx) != 0; }
|
||||
|
||||
// Command line processing
|
||||
bool read_cli(int argc, const char* const argv[], t_config_option_keys* extra, t_config_option_keys* keys = nullptr);
|
||||
|
||||
|
@ -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).
|
||||
|
@ -30,8 +30,8 @@ template<class EP> using AsTraits = Traits<remove_cvref_t<EP>>;
|
||||
|
||||
// Each execution policy should declare two types of mutexes. A a spin lock and
|
||||
// a blocking mutex. These types should satisfy the BasicLockable concept.
|
||||
template<class EP> using SpinningMutex = typename Traits<EP>::SpinningMutex;
|
||||
template<class EP> using BlockingMutex = typename Traits<EP>::BlockingMutex;
|
||||
template<class EP> using SpinningMutex = typename AsTraits<EP>::SpinningMutex;
|
||||
template<class EP> using BlockingMutex = typename AsTraits<EP>::BlockingMutex;
|
||||
|
||||
// Query the available threads for concurrency.
|
||||
template<class EP, class = ExecutionPolicyOnly<EP> >
|
||||
|
@ -63,7 +63,7 @@ void Generator::generateInitialInternalOverhangs(const PrintObject &print_object
|
||||
Polygons infill_area_here;
|
||||
for (const LayerRegion* layerm : print_object.get_layer(layer_nr)->regions())
|
||||
for (const Surface& surface : layerm->fill_surfaces.surfaces)
|
||||
if (surface.surface_type == stInternal)
|
||||
if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid)
|
||||
append(infill_area_here, infill_wall_offset == 0 ? surface.expolygon : offset(surface.expolygon, infill_wall_offset));
|
||||
|
||||
//Remove the part of the infill area that is already supported by the walls.
|
||||
@ -92,7 +92,7 @@ void Generator::generateTrees(const PrintObject &print_object)
|
||||
for (int layer_id = int(print_object.layers().size()) - 1; layer_id >= 0; layer_id--)
|
||||
for (const LayerRegion *layerm : print_object.get_layer(layer_id)->regions())
|
||||
for (const Surface &surface : layerm->fill_surfaces.surfaces)
|
||||
if (surface.surface_type == stInternal)
|
||||
if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid)
|
||||
append(infill_outlines[layer_id], infill_wall_offset == 0 ? surface.expolygon : offset(surface.expolygon, infill_wall_offset));
|
||||
|
||||
// For various operations its beneficial to quickly locate nearby features on the polygon:
|
||||
@ -116,8 +116,12 @@ void Generator::generateTrees(const PrintObject &print_object)
|
||||
if (layer_id == 0)
|
||||
return;
|
||||
|
||||
const Polygons& below_outlines = infill_outlines[layer_id - 1];
|
||||
outlines_locator.set_bbox(get_extents(below_outlines).inflated(SCALED_EPSILON));
|
||||
const Polygons &below_outlines = infill_outlines[layer_id - 1];
|
||||
BoundingBox below_outlines_bbox = get_extents(below_outlines).inflated(SCALED_EPSILON);
|
||||
if (const BoundingBox &outlines_locator_bbox = outlines_locator.bbox(); outlines_locator_bbox.defined)
|
||||
below_outlines_bbox.merge(outlines_locator_bbox);
|
||||
|
||||
outlines_locator.set_bbox(below_outlines_bbox);
|
||||
outlines_locator.create(below_outlines, locator_cell_size);
|
||||
|
||||
std::vector<NodeSPtr>& lower_trees = m_lightning_layers[layer_id - 1].tree_roots;
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include "DistanceField.hpp"
|
||||
#include "TreeNode.hpp"
|
||||
|
||||
#include "../../ClipperUtils.hpp"
|
||||
#include "../../Geometry.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
@ -271,6 +272,7 @@ void Layer::reconnectRoots
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
/*!
|
||||
* Moves the point \p from onto the nearest polygon or leaves the point as-is, when the comb boundary is not within the root of \p max_dist2 distance.
|
||||
* Given a \p distance more than zero, the point will end up inside, and conversely outside.
|
||||
@ -398,6 +400,7 @@ static unsigned int moveInside(const Polygons& polygons, Point& from, int distan
|
||||
}
|
||||
return static_cast<unsigned int>(-1);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Returns 'added someting'.
|
||||
Polylines Layer::convertToLines(const Polygons& limit_to_outline, const coord_t line_width) const
|
||||
@ -405,31 +408,11 @@ Polylines Layer::convertToLines(const Polygons& limit_to_outline, const coord_t
|
||||
if (tree_roots.empty())
|
||||
return {};
|
||||
|
||||
Polygons result_lines;
|
||||
for (const auto& tree : tree_roots) {
|
||||
// If even the furthest location in the tree is inside the polygon, the entire tree must be inside of the polygon.
|
||||
// (Don't take the root as that may be on the edge and cause rounding errors to register as 'outside'.)
|
||||
constexpr coord_t epsilon = 5;
|
||||
Point should_be_inside = tree->getLocation();
|
||||
moveInside(limit_to_outline, should_be_inside, epsilon, epsilon * epsilon);
|
||||
if (inside(limit_to_outline, should_be_inside))
|
||||
tree->convertToPolylines(result_lines, line_width);
|
||||
}
|
||||
Polylines result_lines;
|
||||
for (const auto &tree : tree_roots)
|
||||
tree->convertToPolylines(result_lines, line_width);
|
||||
|
||||
// TODO: allow for polylines!
|
||||
Polylines split_lines;
|
||||
for (Polygon &line : result_lines) {
|
||||
if (line.size() <= 1)
|
||||
continue;
|
||||
Point last = line[0];
|
||||
for (size_t point_idx = 1; point_idx < line.size(); point_idx++) {
|
||||
Point here = line[point_idx];
|
||||
split_lines.push_back({ last, here });
|
||||
last = here;
|
||||
}
|
||||
}
|
||||
|
||||
return split_lines;
|
||||
return intersection_pl(result_lines, limit_to_outline);
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Lightning
|
||||
|
@ -343,16 +343,16 @@ coord_t Node::prune(const coord_t& pruning_distance)
|
||||
return max_distance_pruned;
|
||||
}
|
||||
|
||||
void Node::convertToPolylines(Polygons& output, const coord_t line_width) const
|
||||
void Node::convertToPolylines(Polylines &output, const coord_t line_width) const
|
||||
{
|
||||
Polygons result;
|
||||
Polylines result;
|
||||
result.emplace_back();
|
||||
convertToPolylines(0, result);
|
||||
removeJunctionOverlap(result, line_width);
|
||||
append(output, std::move(result));
|
||||
}
|
||||
|
||||
void Node::convertToPolylines(size_t long_line_idx, Polygons& output) const
|
||||
void Node::convertToPolylines(size_t long_line_idx, Polylines &output) const
|
||||
{
|
||||
if (m_children.empty()) {
|
||||
output[long_line_idx].points.push_back(m_p);
|
||||
@ -372,12 +372,12 @@ void Node::convertToPolylines(size_t long_line_idx, Polygons& output) const
|
||||
}
|
||||
}
|
||||
|
||||
void Node::removeJunctionOverlap(Polygons& result_lines, const coord_t line_width) const
|
||||
void Node::removeJunctionOverlap(Polylines &result_lines, const coord_t line_width) const
|
||||
{
|
||||
const coord_t reduction = line_width / 2; // TODO make configurable?
|
||||
size_t res_line_idx = 0;
|
||||
while (res_line_idx < result_lines.size()) {
|
||||
Polygon &polyline = result_lines[res_line_idx];
|
||||
Polyline &polyline = result_lines[res_line_idx];
|
||||
if (polyline.size() <= 1) {
|
||||
polyline = std::move(result_lines.back());
|
||||
result_lines.pop_back();
|
||||
@ -387,7 +387,7 @@ void Node::removeJunctionOverlap(Polygons& result_lines, const coord_t line_widt
|
||||
coord_t to_be_reduced = reduction;
|
||||
Point a = polyline.back();
|
||||
for (int point_idx = int(polyline.size()) - 2; point_idx >= 0; point_idx--) {
|
||||
const Point b = polyline[point_idx];
|
||||
const Point b = polyline.points[point_idx];
|
||||
const Point ab = b - a;
|
||||
const auto ab_len = coord_t(ab.cast<double>().norm());
|
||||
if (ab_len >= to_be_reduced) {
|
||||
|
@ -239,7 +239,7 @@ public:
|
||||
*
|
||||
* \param output all branches in this tree connected into polylines
|
||||
*/
|
||||
void convertToPolylines(Polygons& output, coord_t line_width) const;
|
||||
void convertToPolylines(Polylines &output, coord_t line_width) const;
|
||||
|
||||
/*! If this was ever a direct child of the root, it'll have a previous grounding location.
|
||||
*
|
||||
@ -258,9 +258,9 @@ protected:
|
||||
* \param long_line a reference to a polyline in \p output which to continue building on in the recursion
|
||||
* \param output all branches in this tree connected into polylines
|
||||
*/
|
||||
void convertToPolylines(size_t long_line_idx, Polygons& output) const;
|
||||
void convertToPolylines(size_t long_line_idx, Polylines &output) const;
|
||||
|
||||
void removeJunctionOverlap(Polygons& polylines, coord_t line_width) const;
|
||||
void removeJunctionOverlap(Polylines &polylines, coord_t line_width) const;
|
||||
|
||||
bool m_is_root;
|
||||
Point m_p;
|
||||
|
@ -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;
|
||||
|
@ -1991,14 +1991,15 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers
|
||||
if (comment == reserved_tag(ETags::Layer_Change)) {
|
||||
++m_layer_id;
|
||||
if (m_spiral_vase_active) {
|
||||
if (m_result.moves.empty())
|
||||
m_result.spiral_vase_layers.push_back({ m_first_layer_height, { 0, 0 } });
|
||||
if (m_result.moves.empty() || m_result.spiral_vase_layers.empty())
|
||||
// add a placeholder for layer height. the actual value will be set inside process_G1() method
|
||||
m_result.spiral_vase_layers.push_back({ FLT_MAX, { 0, 0 } });
|
||||
else {
|
||||
const size_t move_id = m_result.moves.size() - 1;
|
||||
if (!m_result.spiral_vase_layers.empty() && m_end_position[Z] == m_result.spiral_vase_layers.back().first)
|
||||
if (!m_result.spiral_vase_layers.empty())
|
||||
m_result.spiral_vase_layers.back().second.second = move_id;
|
||||
else
|
||||
m_result.spiral_vase_layers.push_back({ static_cast<float>(m_end_position[Z]), { move_id, move_id } });
|
||||
// add a placeholder for layer height. the actual value will be set inside process_G1() method
|
||||
m_result.spiral_vase_layers.push_back({ FLT_MAX, { move_id, move_id } });
|
||||
}
|
||||
}
|
||||
return;
|
||||
@ -2828,8 +2829,13 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
|
||||
m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[m_extruder_id]);
|
||||
}
|
||||
|
||||
if (m_spiral_vase_active && !m_result.spiral_vase_layers.empty() && !m_result.moves.empty())
|
||||
m_result.spiral_vase_layers.back().second.second = m_result.moves.size() - 1;
|
||||
if (m_spiral_vase_active && !m_result.spiral_vase_layers.empty()) {
|
||||
if (m_result.spiral_vase_layers.back().first == FLT_MAX && delta_pos[Z] > 0.0)
|
||||
// replace layer height placeholder with correct value
|
||||
m_result.spiral_vase_layers.back().first = static_cast<float>(m_end_position[Z]);
|
||||
if (!m_result.moves.empty())
|
||||
m_result.spiral_vase_layers.back().second.second = m_result.moves.size() - 1;
|
||||
}
|
||||
|
||||
// store move
|
||||
#if ENABLE_PROCESS_G2_G3_LINES
|
||||
|
@ -1,9 +1,15 @@
|
||||
#ifndef slic3r_Geometry_ConvexHull_hpp_
|
||||
#define slic3r_Geometry_ConvexHull_hpp_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "../Polygon.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ExPolygon;
|
||||
using ExPolygons = std::vector<ExPolygon>;
|
||||
|
||||
namespace Geometry {
|
||||
|
||||
Pointf3s convex_hull(Pointf3s points);
|
||||
|
@ -11,231 +11,276 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
enum class VisitorReturnMask : unsigned int {
|
||||
CONTINUE_LEFT = 1,
|
||||
CONTINUE_RIGHT = 2,
|
||||
STOP = 4,
|
||||
};
|
||||
|
||||
// KD tree for N-dimensional closest point search.
|
||||
template<size_t ANumDimensions, typename ACoordType, typename ACoordinateFn>
|
||||
class KDTreeIndirect
|
||||
{
|
||||
public:
|
||||
static constexpr size_t NumDimensions = ANumDimensions;
|
||||
using CoordinateFn = ACoordinateFn;
|
||||
using CoordType = ACoordType;
|
||||
static constexpr size_t NumDimensions = ANumDimensions;
|
||||
using CoordinateFn = ACoordinateFn;
|
||||
using CoordType = ACoordType;
|
||||
// Following could be static constexpr size_t, but that would not link in C++11
|
||||
enum : size_t {
|
||||
npos = size_t(-1)
|
||||
};
|
||||
|
||||
KDTreeIndirect(CoordinateFn coordinate) : coordinate(coordinate) {}
|
||||
KDTreeIndirect(CoordinateFn coordinate, std::vector<size_t> indices) : coordinate(coordinate) { this->build(std::move(indices)); }
|
||||
KDTreeIndirect(CoordinateFn coordinate, std::vector<size_t> &&indices) : coordinate(coordinate) { this->build(std::move(indices)); }
|
||||
KDTreeIndirect(CoordinateFn coordinate, size_t num_indices) : coordinate(coordinate) { this->build(num_indices); }
|
||||
KDTreeIndirect(KDTreeIndirect &&rhs) : m_nodes(std::move(rhs.m_nodes)), coordinate(std::move(rhs.coordinate)) {}
|
||||
KDTreeIndirect& operator=(KDTreeIndirect &&rhs) { m_nodes = std::move(rhs.m_nodes); coordinate = std::move(rhs.coordinate); return *this; }
|
||||
void clear() { m_nodes.clear(); }
|
||||
KDTreeIndirect(CoordinateFn coordinate) : coordinate(coordinate) {}
|
||||
KDTreeIndirect(CoordinateFn coordinate, std::vector<size_t> indices) : coordinate(coordinate) { this->build(indices); }
|
||||
KDTreeIndirect(CoordinateFn coordinate, size_t num_indices) : coordinate(coordinate) { this->build(num_indices); }
|
||||
KDTreeIndirect(KDTreeIndirect &&rhs) : m_nodes(std::move(rhs.m_nodes)), coordinate(std::move(rhs.coordinate)) {}
|
||||
KDTreeIndirect& operator=(KDTreeIndirect &&rhs) { m_nodes = std::move(rhs.m_nodes); coordinate = std::move(rhs.coordinate); return *this; }
|
||||
void clear() { m_nodes.clear(); }
|
||||
|
||||
void build(size_t num_indices)
|
||||
{
|
||||
std::vector<size_t> indices;
|
||||
indices.reserve(num_indices);
|
||||
for (size_t i = 0; i < num_indices; ++ i)
|
||||
indices.emplace_back(i);
|
||||
this->build(std::move(indices));
|
||||
}
|
||||
void build(size_t num_indices)
|
||||
{
|
||||
std::vector<size_t> indices;
|
||||
indices.reserve(num_indices);
|
||||
for (size_t i = 0; i < num_indices; ++ i)
|
||||
indices.emplace_back(i);
|
||||
this->build(indices);
|
||||
}
|
||||
|
||||
void build(std::vector<size_t> &&indices)
|
||||
{
|
||||
if (indices.empty())
|
||||
clear();
|
||||
else {
|
||||
// Allocate enough memory for a full binary tree.
|
||||
m_nodes.assign(next_highest_power_of_2(indices.size() + 1), npos);
|
||||
build_recursive(indices, 0, 0, 0, indices.size() - 1);
|
||||
}
|
||||
indices.clear();
|
||||
}
|
||||
void build(std::vector<size_t> &indices)
|
||||
{
|
||||
if (indices.empty())
|
||||
clear();
|
||||
else {
|
||||
// Allocate enough memory for a full binary tree.
|
||||
m_nodes.assign(next_highest_power_of_2(indices.size() + 1), npos);
|
||||
build_recursive(indices, 0, 0, 0, indices.size() - 1);
|
||||
}
|
||||
indices.clear();
|
||||
}
|
||||
|
||||
enum class VisitorReturnMask : unsigned int
|
||||
{
|
||||
CONTINUE_LEFT = 1,
|
||||
CONTINUE_RIGHT = 2,
|
||||
STOP = 4,
|
||||
};
|
||||
template<typename CoordType>
|
||||
unsigned int descent_mask(const CoordType &point_coord, const CoordType &search_radius, size_t idx, size_t dimension) const
|
||||
{
|
||||
CoordType dist = point_coord - this->coordinate(idx, dimension);
|
||||
return (dist * dist < search_radius + CoordType(EPSILON)) ?
|
||||
// The plane intersects a hypersphere centered at point_coord of search_radius.
|
||||
((unsigned int)(VisitorReturnMask::CONTINUE_LEFT) | (unsigned int)(VisitorReturnMask::CONTINUE_RIGHT)) :
|
||||
// The plane does not intersect the hypersphere.
|
||||
(dist > CoordType(0)) ? (unsigned int)(VisitorReturnMask::CONTINUE_RIGHT) : (unsigned int)(VisitorReturnMask::CONTINUE_LEFT);
|
||||
}
|
||||
template<typename CoordType>
|
||||
unsigned int descent_mask(const CoordType &point_coord, const CoordType &search_radius, size_t idx, size_t dimension) const
|
||||
{
|
||||
CoordType dist = point_coord - this->coordinate(idx, dimension);
|
||||
return (dist * dist < search_radius + CoordType(EPSILON)) ?
|
||||
// The plane intersects a hypersphere centered at point_coord of search_radius.
|
||||
((unsigned int)(VisitorReturnMask::CONTINUE_LEFT) | (unsigned int)(VisitorReturnMask::CONTINUE_RIGHT)) :
|
||||
// The plane does not intersect the hypersphere.
|
||||
(dist > CoordType(0)) ? (unsigned int)(VisitorReturnMask::CONTINUE_RIGHT) : (unsigned int)(VisitorReturnMask::CONTINUE_LEFT);
|
||||
}
|
||||
|
||||
// Visitor is supposed to return a bit mask of VisitorReturnMask.
|
||||
template<typename Visitor>
|
||||
void visit(Visitor &visitor) const
|
||||
{
|
||||
// Visitor is supposed to return a bit mask of VisitorReturnMask.
|
||||
template<typename Visitor>
|
||||
void visit(Visitor &visitor) const
|
||||
{
|
||||
visit_recursive(0, 0, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
CoordinateFn coordinate;
|
||||
CoordinateFn coordinate;
|
||||
|
||||
private:
|
||||
// Build a balanced tree by splitting the input sequence by an axis aligned plane at a dimension.
|
||||
void build_recursive(std::vector<size_t> &input, size_t node, const size_t dimension, const size_t left, const size_t right)
|
||||
{
|
||||
if (left > right)
|
||||
return;
|
||||
// Build a balanced tree by splitting the input sequence by an axis aligned plane at a dimension.
|
||||
void build_recursive(std::vector<size_t> &input, size_t node, const size_t dimension, const size_t left, const size_t right)
|
||||
{
|
||||
if (left > right)
|
||||
return;
|
||||
|
||||
assert(node < m_nodes.size());
|
||||
assert(node < m_nodes.size());
|
||||
|
||||
if (left == right) {
|
||||
// Insert a node into the balanced tree.
|
||||
m_nodes[node] = input[left];
|
||||
return;
|
||||
}
|
||||
if (left == right) {
|
||||
// Insert a node into the balanced tree.
|
||||
m_nodes[node] = input[left];
|
||||
return;
|
||||
}
|
||||
|
||||
// Partition the input to left / right pieces of the same length to produce a balanced tree.
|
||||
size_t center = (left + right) / 2;
|
||||
partition_input(input, dimension, left, right, center);
|
||||
// Insert a node into the tree.
|
||||
m_nodes[node] = input[center];
|
||||
// Build up the left / right subtrees.
|
||||
size_t next_dimension = dimension;
|
||||
if (++ next_dimension == NumDimensions)
|
||||
next_dimension = 0;
|
||||
if (center > left)
|
||||
build_recursive(input, node * 2 + 1, next_dimension, left, center - 1);
|
||||
build_recursive(input, node * 2 + 2, next_dimension, center + 1, right);
|
||||
}
|
||||
// Partition the input to left / right pieces of the same length to produce a balanced tree.
|
||||
size_t center = (left + right) / 2;
|
||||
partition_input(input, dimension, left, right, center);
|
||||
// Insert a node into the tree.
|
||||
m_nodes[node] = input[center];
|
||||
// Build up the left / right subtrees.
|
||||
size_t next_dimension = dimension;
|
||||
if (++ next_dimension == NumDimensions)
|
||||
next_dimension = 0;
|
||||
if (center > left)
|
||||
build_recursive(input, node * 2 + 1, next_dimension, left, center - 1);
|
||||
build_recursive(input, node * 2 + 2, next_dimension, center + 1, right);
|
||||
}
|
||||
|
||||
// Partition the input m_nodes <left, right> at "k" and "dimension" using the QuickSelect method:
|
||||
// https://en.wikipedia.org/wiki/Quickselect
|
||||
// Items left of the k'th item are lower than the k'th item in the "dimension",
|
||||
// items right of the k'th item are higher than the k'th item in the "dimension",
|
||||
void partition_input(std::vector<size_t> &input, const size_t dimension, size_t left, size_t right, const size_t k) const
|
||||
{
|
||||
while (left < right) {
|
||||
size_t center = (left + right) / 2;
|
||||
CoordType pivot;
|
||||
{
|
||||
// Bubble sort the input[left], input[center], input[right], so that a median of the three values
|
||||
// will end up in input[center].
|
||||
CoordType left_value = this->coordinate(input[left], dimension);
|
||||
CoordType center_value = this->coordinate(input[center], dimension);
|
||||
CoordType right_value = this->coordinate(input[right], dimension);
|
||||
if (left_value > center_value) {
|
||||
std::swap(input[left], input[center]);
|
||||
std::swap(left_value, center_value);
|
||||
}
|
||||
if (left_value > right_value) {
|
||||
std::swap(input[left], input[right]);
|
||||
right_value = left_value;
|
||||
}
|
||||
if (center_value > right_value) {
|
||||
std::swap(input[center], input[right]);
|
||||
center_value = right_value;
|
||||
}
|
||||
pivot = center_value;
|
||||
}
|
||||
if (right <= left + 2)
|
||||
// The <left, right> interval is already sorted.
|
||||
break;
|
||||
size_t i = left;
|
||||
size_t j = right - 1;
|
||||
std::swap(input[center], input[j]);
|
||||
// Partition the set based on the pivot.
|
||||
for (;;) {
|
||||
// Skip left points that are already at correct positions.
|
||||
// Search will certainly stop at position (right - 1), which stores the pivot.
|
||||
while (this->coordinate(input[++ i], dimension) < pivot) ;
|
||||
// Skip right points that are already at correct positions.
|
||||
while (this->coordinate(input[-- j], dimension) > pivot && i < j) ;
|
||||
if (i >= j)
|
||||
break;
|
||||
std::swap(input[i], input[j]);
|
||||
}
|
||||
// Restore pivot to the center of the sequence.
|
||||
std::swap(input[i], input[right - 1]);
|
||||
// Which side the kth element is in?
|
||||
if (k < i)
|
||||
right = i - 1;
|
||||
else if (k == i)
|
||||
// Sequence is partitioned, kth element is at its place.
|
||||
break;
|
||||
else
|
||||
left = i + 1;
|
||||
}
|
||||
}
|
||||
// Partition the input m_nodes <left, right> at "k" and "dimension" using the QuickSelect method:
|
||||
// https://en.wikipedia.org/wiki/Quickselect
|
||||
// Items left of the k'th item are lower than the k'th item in the "dimension",
|
||||
// items right of the k'th item are higher than the k'th item in the "dimension",
|
||||
void partition_input(std::vector<size_t> &input, const size_t dimension, size_t left, size_t right, const size_t k) const
|
||||
{
|
||||
while (left < right) {
|
||||
size_t center = (left + right) / 2;
|
||||
CoordType pivot;
|
||||
{
|
||||
// Bubble sort the input[left], input[center], input[right], so that a median of the three values
|
||||
// will end up in input[center].
|
||||
CoordType left_value = this->coordinate(input[left], dimension);
|
||||
CoordType center_value = this->coordinate(input[center], dimension);
|
||||
CoordType right_value = this->coordinate(input[right], dimension);
|
||||
if (left_value > center_value) {
|
||||
std::swap(input[left], input[center]);
|
||||
std::swap(left_value, center_value);
|
||||
}
|
||||
if (left_value > right_value) {
|
||||
std::swap(input[left], input[right]);
|
||||
right_value = left_value;
|
||||
}
|
||||
if (center_value > right_value) {
|
||||
std::swap(input[center], input[right]);
|
||||
center_value = right_value;
|
||||
}
|
||||
pivot = center_value;
|
||||
}
|
||||
if (right <= left + 2)
|
||||
// The <left, right> interval is already sorted.
|
||||
break;
|
||||
size_t i = left;
|
||||
size_t j = right - 1;
|
||||
std::swap(input[center], input[j]);
|
||||
// Partition the set based on the pivot.
|
||||
for (;;) {
|
||||
// Skip left points that are already at correct positions.
|
||||
// Search will certainly stop at position (right - 1), which stores the pivot.
|
||||
while (this->coordinate(input[++ i], dimension) < pivot) ;
|
||||
// Skip right points that are already at correct positions.
|
||||
while (this->coordinate(input[-- j], dimension) > pivot && i < j) ;
|
||||
if (i >= j)
|
||||
break;
|
||||
std::swap(input[i], input[j]);
|
||||
}
|
||||
// Restore pivot to the center of the sequence.
|
||||
std::swap(input[i], input[right - 1]);
|
||||
// Which side the kth element is in?
|
||||
if (k < i)
|
||||
right = i - 1;
|
||||
else if (k == i)
|
||||
// Sequence is partitioned, kth element is at its place.
|
||||
break;
|
||||
else
|
||||
left = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Visitor>
|
||||
void visit_recursive(size_t node, size_t dimension, Visitor &visitor) const
|
||||
{
|
||||
assert(! m_nodes.empty());
|
||||
if (node >= m_nodes.size() || m_nodes[node] == npos)
|
||||
return;
|
||||
template<typename Visitor>
|
||||
void visit_recursive(size_t node, size_t dimension, Visitor &visitor) const
|
||||
{
|
||||
assert(! m_nodes.empty());
|
||||
if (node >= m_nodes.size() || m_nodes[node] == npos)
|
||||
return;
|
||||
|
||||
// Left / right child node index.
|
||||
size_t left = node * 2 + 1;
|
||||
size_t right = left + 1;
|
||||
unsigned int mask = visitor(m_nodes[node], dimension);
|
||||
if ((mask & (unsigned int)VisitorReturnMask::STOP) == 0) {
|
||||
size_t next_dimension = (++ dimension == NumDimensions) ? 0 : dimension;
|
||||
if (mask & (unsigned int)VisitorReturnMask::CONTINUE_LEFT)
|
||||
visit_recursive(left, next_dimension, visitor);
|
||||
if (mask & (unsigned int)VisitorReturnMask::CONTINUE_RIGHT)
|
||||
visit_recursive(right, next_dimension, visitor);
|
||||
}
|
||||
}
|
||||
// Left / right child node index.
|
||||
size_t left = node * 2 + 1;
|
||||
size_t right = left + 1;
|
||||
unsigned int mask = visitor(m_nodes[node], dimension);
|
||||
if ((mask & (unsigned int)VisitorReturnMask::STOP) == 0) {
|
||||
size_t next_dimension = (++ dimension == NumDimensions) ? 0 : dimension;
|
||||
if (mask & (unsigned int)VisitorReturnMask::CONTINUE_LEFT)
|
||||
visit_recursive(left, next_dimension, visitor);
|
||||
if (mask & (unsigned int)VisitorReturnMask::CONTINUE_RIGHT)
|
||||
visit_recursive(right, next_dimension, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<size_t> m_nodes;
|
||||
std::vector<size_t> m_nodes;
|
||||
};
|
||||
|
||||
// Find a closest point using Euclidian metrics.
|
||||
// Returns npos if not found.
|
||||
template<typename KDTreeIndirectType, typename PointType, typename FilterFn>
|
||||
size_t find_closest_point(const KDTreeIndirectType &kdtree, const PointType &point, FilterFn filter)
|
||||
template<size_t K,
|
||||
typename PointType,
|
||||
typename FilterFn,
|
||||
size_t D,
|
||||
typename CoordT,
|
||||
typename CoordFn>
|
||||
std::array<size_t, K> find_closest_points(
|
||||
const KDTreeIndirect<D, CoordT, CoordFn> &kdtree,
|
||||
const PointType &point,
|
||||
FilterFn filter)
|
||||
{
|
||||
using CoordType = typename KDTreeIndirectType::CoordType;
|
||||
using Tree = KDTreeIndirect<D, CoordT, CoordFn>;
|
||||
|
||||
struct Visitor {
|
||||
const KDTreeIndirectType &kdtree;
|
||||
const PointType &point;
|
||||
const FilterFn filter;
|
||||
size_t min_idx = KDTreeIndirectType::npos;
|
||||
CoordType min_dist = std::numeric_limits<CoordType>::max();
|
||||
struct Visitor
|
||||
{
|
||||
const Tree &kdtree;
|
||||
const PointType &point;
|
||||
const FilterFn filter;
|
||||
|
||||
Visitor(const KDTreeIndirectType &kdtree, const PointType &point, FilterFn filter) : kdtree(kdtree), point(point), filter(filter) {}
|
||||
unsigned int operator()(size_t idx, size_t dimension) {
|
||||
if (this->filter(idx)) {
|
||||
auto dist = CoordType(0);
|
||||
for (size_t i = 0; i < KDTreeIndirectType::NumDimensions; ++ i) {
|
||||
CoordType d = point[i] - kdtree.coordinate(idx, i);
|
||||
dist += d * d;
|
||||
}
|
||||
if (dist < min_dist) {
|
||||
min_dist = dist;
|
||||
min_idx = idx;
|
||||
}
|
||||
}
|
||||
return kdtree.descent_mask(point[dimension], min_dist, idx, dimension);
|
||||
}
|
||||
} visitor(kdtree, point, filter);
|
||||
std::array<std::pair<size_t, CoordT>, K> results;
|
||||
|
||||
kdtree.visit(visitor);
|
||||
return visitor.min_idx;
|
||||
Visitor(const Tree &kdtree, const PointType &point, FilterFn filter)
|
||||
: kdtree(kdtree), point(point), filter(filter)
|
||||
{
|
||||
results.fill(std::make_pair(Tree::npos,
|
||||
std::numeric_limits<CoordT>::max()));
|
||||
}
|
||||
unsigned int operator()(size_t idx, size_t dimension)
|
||||
{
|
||||
if (this->filter(idx)) {
|
||||
auto dist = CoordT(0);
|
||||
for (size_t i = 0; i < D; ++i) {
|
||||
CoordT d = point[i] - kdtree.coordinate(idx, i);
|
||||
dist += d * d;
|
||||
}
|
||||
|
||||
auto res = std::make_pair(idx, dist);
|
||||
auto it = std::lower_bound(results.begin(), results.end(),
|
||||
res, [](auto &r1, auto &r2) {
|
||||
return r1.second < r2.second;
|
||||
});
|
||||
|
||||
if (it != results.end()) {
|
||||
std::rotate(it, std::prev(results.end()), results.end());
|
||||
*it = res;
|
||||
}
|
||||
}
|
||||
return kdtree.descent_mask(point[dimension],
|
||||
results.front().second, idx,
|
||||
dimension);
|
||||
}
|
||||
} visitor(kdtree, point, filter);
|
||||
|
||||
kdtree.visit(visitor);
|
||||
std::array<size_t, K> ret;
|
||||
for (size_t i = 0; i < K; i++) ret[i] = visitor.results[i].first;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<size_t K, typename PointType, size_t D, typename CoordT, typename CoordFn>
|
||||
std::array<size_t, K> find_closest_points(
|
||||
const KDTreeIndirect<D, CoordT, CoordFn> &kdtree, const PointType &point)
|
||||
{
|
||||
return find_closest_points<K>(kdtree, point, [](size_t) { return true; });
|
||||
}
|
||||
|
||||
template<typename PointType,
|
||||
typename FilterFn,
|
||||
size_t D,
|
||||
typename CoordT,
|
||||
typename CoordFn>
|
||||
size_t find_closest_point(const KDTreeIndirect<D, CoordT, CoordFn> &kdtree,
|
||||
const PointType &point,
|
||||
FilterFn filter)
|
||||
{
|
||||
return find_closest_points<1>(kdtree, point, filter)[0];
|
||||
}
|
||||
|
||||
template<typename KDTreeIndirectType, typename PointType>
|
||||
size_t find_closest_point(const KDTreeIndirectType& kdtree, const PointType& point)
|
||||
{
|
||||
return find_closest_point(kdtree, point, [](size_t) { return true; });
|
||||
return find_closest_point(kdtree, point, [](size_t) { return true; });
|
||||
}
|
||||
|
||||
// Find nearby points (spherical neighbourhood) using Euclidian metrics.
|
||||
template<typename KDTreeIndirectType, typename PointType, typename FilterFn>
|
||||
std::vector<size_t> find_nearby_points(const KDTreeIndirectType &kdtree, const PointType ¢er,
|
||||
const typename KDTreeIndirectType::CoordType& max_distance, FilterFn filter)
|
||||
{
|
||||
const typename KDTreeIndirectType::CoordType& max_distance, FilterFn filter)
|
||||
{
|
||||
using CoordType = typename KDTreeIndirectType::CoordType;
|
||||
|
||||
struct Visitor {
|
||||
@ -247,7 +292,7 @@ std::vector<size_t> find_nearby_points(const KDTreeIndirectType &kdtree, const P
|
||||
|
||||
Visitor(const KDTreeIndirectType &kdtree, const PointType& center, const CoordType &max_distance,
|
||||
FilterFn filter) :
|
||||
kdtree(kdtree), center(center), max_distance_squared(max_distance*max_distance), filter(filter) {
|
||||
kdtree(kdtree), center(center), max_distance_squared(max_distance*max_distance), filter(filter) {
|
||||
}
|
||||
unsigned int operator()(size_t idx, size_t dimension) {
|
||||
if (this->filter(idx)) {
|
||||
@ -260,7 +305,7 @@ std::vector<size_t> find_nearby_points(const KDTreeIndirectType &kdtree, const P
|
||||
result.push_back(idx);
|
||||
}
|
||||
}
|
||||
return kdtree.descent_mask(center[dimension], max_distance_squared, idx, dimension);
|
||||
return kdtree.descent_mask(center[dimension], max_distance_squared, idx, dimension);
|
||||
}
|
||||
} visitor(kdtree, center, max_distance, filter);
|
||||
|
||||
@ -270,13 +315,59 @@ std::vector<size_t> find_nearby_points(const KDTreeIndirectType &kdtree, const P
|
||||
|
||||
template<typename KDTreeIndirectType, typename PointType>
|
||||
std::vector<size_t> find_nearby_points(const KDTreeIndirectType &kdtree, const PointType ¢er,
|
||||
const typename KDTreeIndirectType::CoordType& max_distance)
|
||||
{
|
||||
const typename KDTreeIndirectType::CoordType& max_distance)
|
||||
{
|
||||
return find_nearby_points(kdtree, center, max_distance, [](size_t) {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// Find nearby points (spherical neighbourhood) using Euclidian metrics.
|
||||
template<typename KDTreeIndirectType, typename PointType, typename FilterFn>
|
||||
std::vector<size_t> find_nearby_points(const KDTreeIndirectType &kdtree,
|
||||
const PointType &bb_min,
|
||||
const PointType &bb_max,
|
||||
FilterFn filter)
|
||||
{
|
||||
struct Visitor {
|
||||
const KDTreeIndirectType &kdtree;
|
||||
const PointType &bb_min, &bb_max;
|
||||
const FilterFn filter;
|
||||
std::vector<size_t> result;
|
||||
|
||||
Visitor(const KDTreeIndirectType &kdtree, const PointType& bbmin, const PointType& bbmax,
|
||||
FilterFn filter) :
|
||||
kdtree(kdtree), bb_min{bbmin}, bb_max{bbmax}, filter(filter) {
|
||||
}
|
||||
unsigned int operator()(size_t idx, size_t dimension) {
|
||||
unsigned int ret =
|
||||
static_cast<unsigned int>(VisitorReturnMask::CONTINUE_LEFT) |
|
||||
static_cast<unsigned int>(VisitorReturnMask::CONTINUE_RIGHT);
|
||||
|
||||
if (this->filter(idx)) {
|
||||
PointType p;
|
||||
bool contains = true;
|
||||
for (size_t i = 0; i < KDTreeIndirectType::NumDimensions; ++i) {
|
||||
p(i) = kdtree.coordinate(idx, i);
|
||||
contains = contains && bb_min(i) <= p(i) && p(i) <= bb_max(i);
|
||||
}
|
||||
|
||||
if (p(dimension) < bb_min(dimension))
|
||||
ret = static_cast<unsigned int>(VisitorReturnMask::CONTINUE_RIGHT);
|
||||
if (p(dimension) > bb_max(dimension))
|
||||
ret = static_cast<unsigned int>(VisitorReturnMask::CONTINUE_LEFT);
|
||||
|
||||
if (contains)
|
||||
result.emplace_back(idx);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
} visitor(kdtree, bb_min, bb_max, filter);
|
||||
|
||||
kdtree.visit(visitor);
|
||||
return visitor.result;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
|
@ -41,13 +41,13 @@ template<size_t N> using Bounds = std::array<Bound, N>;
|
||||
class StopCriteria {
|
||||
|
||||
// If the absolute value difference between two scores.
|
||||
double m_abs_score_diff = std::nan("");
|
||||
double m_abs_score_diff = NaNd;
|
||||
|
||||
// If the relative value difference between two scores.
|
||||
double m_rel_score_diff = std::nan("");
|
||||
double m_rel_score_diff = NaNd;
|
||||
|
||||
// Stop if this value or better is found.
|
||||
double m_stop_score = std::nan("");
|
||||
double m_stop_score = NaNd;
|
||||
|
||||
// A predicate that if evaluates to true, the optimization should terminate
|
||||
// and the best result found prior to termination should be returned.
|
||||
|
74
src/libslic3r/PointGrid.hpp
Normal file
74
src/libslic3r/PointGrid.hpp
Normal file
@ -0,0 +1,74 @@
|
||||
#ifndef POINTGRID_HPP
|
||||
#define POINTGRID_HPP
|
||||
|
||||
#include <libslic3r/Execution/Execution.hpp>
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include <libslic3r/BoundingBox.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
template<class T>
|
||||
class PointGrid {
|
||||
Vec3i m_size;
|
||||
std::vector<Vec<3, T>> m_data;
|
||||
const int XY;
|
||||
|
||||
public:
|
||||
explicit PointGrid(std::vector<Vec<3, T>> data, const Vec3i &size)
|
||||
: m_data(std::move(data)), m_size{size}, XY{m_size.x() * m_size.y()}
|
||||
{}
|
||||
|
||||
const Vec<3, T> & get(size_t idx) const { return m_data[idx]; }
|
||||
const Vec<3, T> & get(const Vec3i &coord) const
|
||||
{
|
||||
return m_data[get_idx(coord)];
|
||||
}
|
||||
|
||||
size_t get_idx(const Vec3i &coord) const
|
||||
{
|
||||
size_t ret = coord.z() * XY + coord.y() * m_size.x() + coord.x();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Vec3i get_coord(size_t idx) const {
|
||||
size_t iz = idx / XY;
|
||||
size_t iy = (idx / m_size.x()) % m_size.y();
|
||||
size_t ix = idx % m_size.x();
|
||||
|
||||
return {ix, iy, iz};
|
||||
}
|
||||
|
||||
const std::vector<Vec<3, T>> & data() const { return m_data; }
|
||||
size_t point_count() const { return m_data.size(); }
|
||||
bool empty() const { return m_data.empty(); }
|
||||
};
|
||||
|
||||
template<class Ex, class CoordT>
|
||||
PointGrid<CoordT> point_grid(Ex policy,
|
||||
const BoundingBox3Base<Vec<3, CoordT>> &bounds,
|
||||
const Vec<3, CoordT> &stride)
|
||||
{
|
||||
Vec3i numpts = Vec3i::Zero();
|
||||
|
||||
for (int n = 0; n < 3; ++n)
|
||||
numpts(n) = (bounds.max(n) - bounds.min(n)) / stride(n);
|
||||
|
||||
std::vector<Vec<3, CoordT>> out(numpts.x() * numpts.y() * numpts.z());
|
||||
|
||||
size_t XY = numpts[X] * numpts[Y];
|
||||
|
||||
execution::for_each(policy, size_t(0), out.size(), [&](size_t i) {
|
||||
size_t iz = i / XY;
|
||||
size_t iy = (i / numpts[X]) % numpts[Y];
|
||||
size_t ix = i % numpts[X];
|
||||
|
||||
out[i] = Vec<3, CoordT>(ix * stride.x(), iy * stride.y(), iz * stride.z());
|
||||
});
|
||||
|
||||
return PointGrid{std::move(out), numpts};
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // POINTGRID_HPP
|
@ -220,6 +220,16 @@ public:
|
||||
config.set_deserialize_strict(items);
|
||||
return config;
|
||||
}
|
||||
static DynamicPrintConfig new_with(const t_config_option_key &opt_key, const std::string &str, bool append = false) {
|
||||
DynamicPrintConfig config;
|
||||
config.set_deserialize_strict(opt_key, str, append);
|
||||
return config;
|
||||
}
|
||||
static DynamicPrintConfig new_with(std::initializer_list<SetDeserializeItem> items) {
|
||||
DynamicPrintConfig config;
|
||||
config.set_deserialize_strict(items);
|
||||
return config;
|
||||
}
|
||||
static DynamicPrintConfig* new_from_defaults_keys(const std::vector<std::string> &keys);
|
||||
|
||||
// Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here.
|
||||
@ -1131,6 +1141,8 @@ public:
|
||||
void set(const std::string &opt_key, T value) { m_data.set(opt_key, value, true); this->touch(); }
|
||||
void set_deserialize(const t_config_option_key &opt_key, const std::string &str, ConfigSubstitutionContext &substitution_context, bool append = false)
|
||||
{ m_data.set_deserialize(opt_key, str, substitution_context, append); this->touch(); }
|
||||
void set_deserialize_strict(const t_config_option_key &opt_key, const std::string &str, bool append = false)
|
||||
{ m_data.set_deserialize_strict(opt_key, str, append); this->touch(); }
|
||||
bool erase(const t_config_option_key &opt_key) { bool out = m_data.erase(opt_key); if (out) this->touch(); return out; }
|
||||
|
||||
// Getters are thread safe.
|
||||
|
@ -76,7 +76,7 @@ struct Facestats {
|
||||
// Try to guess the number of support points needed to support a mesh
|
||||
double get_misalginment_score(const TriangleMesh &mesh, const Transform3f &tr)
|
||||
{
|
||||
if (mesh.its.vertices.empty()) return std::nan("");
|
||||
if (mesh.its.vertices.empty()) return NaNd;
|
||||
|
||||
auto accessfn = [&mesh, &tr](size_t fi) {
|
||||
Facestats fc{get_transformed_triangle(mesh, tr, fi)};
|
||||
@ -117,7 +117,7 @@ inline double get_supportedness_score(const Facestats &fc)
|
||||
// Try to guess the number of support points needed to support a mesh
|
||||
double get_supportedness_score(const TriangleMesh &mesh, const Transform3f &tr)
|
||||
{
|
||||
if (mesh.its.vertices.empty()) return std::nan("");
|
||||
if (mesh.its.vertices.empty()) return NaNd;
|
||||
|
||||
auto accessfn = [&mesh, &tr](size_t fi) {
|
||||
Facestats fc{get_transformed_triangle(mesh, tr, fi)};
|
||||
@ -149,10 +149,10 @@ float find_ground_level(const TriangleMesh &mesh,
|
||||
return execution::reduce(ex_tbb, size_t(0), vsize, zmin, minfn, accessfn, granularity);
|
||||
}
|
||||
|
||||
float get_supportedness_onfloor_score(const TriangleMesh &mesh,
|
||||
const Transform3f & tr)
|
||||
double get_supportedness_onfloor_score(const TriangleMesh &mesh,
|
||||
const Transform3f &tr)
|
||||
{
|
||||
if (mesh.its.vertices.empty()) return std::nan("");
|
||||
if (mesh.its.vertices.empty()) return NaNd;
|
||||
|
||||
size_t Nthreads = std::thread::hardware_concurrency();
|
||||
|
||||
|
@ -654,7 +654,7 @@ void SupportTreeBuildsteps::filter()
|
||||
for (const SupportPoint &sp : m_support_pts) {
|
||||
m_thr();
|
||||
heads.emplace_back(
|
||||
std::nan(""),
|
||||
NaNd,
|
||||
sp.head_front_radius,
|
||||
0.,
|
||||
m_cfg.head_penetration_mm,
|
||||
|
@ -408,9 +408,9 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po)
|
||||
AABBTreeIndirect::traverse(
|
||||
tree,
|
||||
AABBTreeIndirect::intersecting(ebb),
|
||||
[&part_to_drill, &hollowed_mesh](size_t faceid)
|
||||
[&part_to_drill, &hollowed_mesh](const auto& node)
|
||||
{
|
||||
part_to_drill.indices.emplace_back(hollowed_mesh.its.indices[faceid]);
|
||||
part_to_drill.indices.emplace_back(hollowed_mesh.its.indices[node.idx]);
|
||||
});
|
||||
|
||||
auto cgal_meshpart = MeshBoolean::cgal::triangle_mesh_to_cgal(
|
||||
@ -1036,7 +1036,7 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() {
|
||||
// Estimated printing time
|
||||
// A layers count o the highest object
|
||||
if (printer_input.size() == 0)
|
||||
print_statistics.estimated_print_time = std::nan("");
|
||||
print_statistics.estimated_print_time = NaNd;
|
||||
else {
|
||||
print_statistics.estimated_print_time = estim_time;
|
||||
print_statistics.layers_times = layers_times;
|
||||
|
@ -331,6 +331,12 @@ public:
|
||||
inline bool empty() const { return size() == 0; }
|
||||
};
|
||||
|
||||
template<class T, class = FloatingOnly<T>>
|
||||
constexpr T NaN = std::numeric_limits<T>::quiet_NaN();
|
||||
|
||||
constexpr float NaNf = NaN<float>;
|
||||
constexpr double NaNd = NaN<double>;
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "libslic3r/Platform.hpp"
|
||||
#include "libslic3r/Config.hpp"
|
||||
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/dll/runtime_symbol_info.hpp>
|
||||
@ -503,4 +504,4 @@ DesktopIntegrationDialog::~DesktopIntegrationDialog()
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
#endif // __linux__
|
||||
#endif // __linux__
|
||||
|
@ -1163,7 +1163,7 @@ void Control::draw_ruler(wxDC& dc)
|
||||
}
|
||||
};
|
||||
|
||||
double short_tick = std::nan("");
|
||||
double short_tick = NaNd;
|
||||
int tick = 0;
|
||||
double value = 0.0;
|
||||
size_t sequence = 0;
|
||||
|
@ -3942,7 +3942,7 @@ void GCodeViewer::render_legend(float& legend_height)
|
||||
|
||||
std::vector<CustomGCode::Item> custom_gcode_per_print_z = wxGetApp().is_editor() ? wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes : m_custom_gcode_per_print_z;
|
||||
std::vector<ColorRGBA> last_color(m_extruders_count);
|
||||
for (int i = 0; i < m_extruders_count; ++i) {
|
||||
for (size_t i = 0; i < m_extruders_count; ++i) {
|
||||
last_color[i] = m_tool_colors[i];
|
||||
}
|
||||
int last_extruder_id = 1;
|
||||
|
@ -822,7 +822,7 @@ public:
|
||||
|
||||
class WipeTowerInfo {
|
||||
protected:
|
||||
Vec2d m_pos = {std::nan(""), std::nan("")};
|
||||
Vec2d m_pos = {NaNd, NaNd};
|
||||
double m_rotation = 0.;
|
||||
BoundingBoxf m_bb;
|
||||
friend class GLCanvas3D;
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include <cstdlib>
|
||||
#include <regex>
|
||||
#include <string_view>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/format.hpp>
|
||||
|
@ -338,10 +338,12 @@ void OptionsGroup::activate_line(Line& line)
|
||||
wxBOTTOM | wxTOP | (option.opt.full_width ? int(wxEXPAND) : int(wxALIGN_CENTER_VERTICAL)), (wxOSX || !staticbox) ? 0 : 2);
|
||||
if (is_sizer_field(field))
|
||||
sizer->Add(field->getSizer(), 1, (option.opt.full_width ? int(wxEXPAND) : int(wxALIGN_CENTER_VERTICAL)), 0);
|
||||
}
|
||||
} else
|
||||
delete sizer;
|
||||
return;
|
||||
}
|
||||
|
||||
bool sizer_is_used = false;
|
||||
bool is_multioption_line = option_set.size() > 1;
|
||||
for (auto opt : option_set) {
|
||||
ConfigOptionDef option = opt.opt;
|
||||
@ -356,8 +358,9 @@ void OptionsGroup::activate_line(Line& line)
|
||||
wxSize(sublabel_width != -1 ? sublabel_width * wxGetApp().em_unit() : -1, -1), wxALIGN_RIGHT);
|
||||
label->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
label->SetFont(wxGetApp().normal_font());
|
||||
sizer_tmp->Add(label, 0, wxALIGN_CENTER_VERTICAL, 0);
|
||||
}
|
||||
sizer_tmp->Add(label, 0, wxALIGN_CENTER_VERTICAL, 0);
|
||||
sizer_is_used = true;
|
||||
}
|
||||
|
||||
// add field
|
||||
const Option& opt_ref = opt;
|
||||
@ -412,6 +415,9 @@ void OptionsGroup::activate_line(Line& line)
|
||||
if (!custom_ctrl)
|
||||
sizer->Add(line.extra_widget_sizer, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4); //! requires verification
|
||||
}
|
||||
|
||||
if (custom_ctrl && !sizer_is_used)
|
||||
delete sizer;
|
||||
}
|
||||
|
||||
// create all controls for the option group from the m_lines
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "Notebook.hpp"
|
||||
#include "ButtonsDescription.hpp"
|
||||
#include "OG_CustomCtrl.hpp"
|
||||
#include "GLCanvas3D.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@ -54,6 +55,14 @@ PreferencesDialog::PreferencesDialog(wxWindow* parent) :
|
||||
m_highlighter.set_timer_owner(this, 0);
|
||||
}
|
||||
|
||||
static void update_color(wxColourPickerCtrl* color_pckr, const wxColour& color)
|
||||
{
|
||||
if (color_pckr->GetColour() != color) {
|
||||
color_pckr->SetColour(color);
|
||||
wxPostEvent(color_pckr, wxCommandEvent(wxEVT_COLOURPICKER_CHANGED));
|
||||
}
|
||||
}
|
||||
|
||||
void PreferencesDialog::show(const std::string& highlight_opt_key /*= std::string()*/, const std::string& tab_name/*= std::string()*/)
|
||||
{
|
||||
int selected_tab = 0;
|
||||
@ -66,6 +75,14 @@ void PreferencesDialog::show(const std::string& highlight_opt_key /*= std::strin
|
||||
if (!highlight_opt_key.empty())
|
||||
init_highlighter(highlight_opt_key);
|
||||
|
||||
// cache input values for custom toolbar size
|
||||
m_custom_toolbar_size = atoi(get_app_config()->get("custom_toolbar_size").c_str());
|
||||
m_use_custom_toolbar_size = get_app_config()->get("use_custom_toolbar_size") == "1";
|
||||
|
||||
// update colors for color pickers
|
||||
update_color(m_sys_colour, wxGetApp().get_label_clr_sys());
|
||||
update_color(m_mod_colour, wxGetApp().get_label_clr_modified());
|
||||
|
||||
this->ShowModal();
|
||||
}
|
||||
|
||||
@ -173,6 +190,10 @@ void PreferencesDialog::build()
|
||||
// Add "General" tab
|
||||
m_optgroup_general = create_options_tab(L("General"), tabs);
|
||||
m_optgroup_general->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
|
||||
if (auto it = m_values.find(opt_key); it != m_values.end()) {
|
||||
m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected
|
||||
return;
|
||||
}
|
||||
if (opt_key == "default_action_on_close_application" || opt_key == "default_action_on_select_preset" || opt_key == "default_action_on_new_project")
|
||||
m_values[opt_key] = boost::any_cast<bool>(value) ? "none" : "discard";
|
||||
else if (opt_key == "default_action_on_dirty_project")
|
||||
@ -335,6 +356,10 @@ void PreferencesDialog::build()
|
||||
// Add "Camera" tab
|
||||
m_optgroup_camera = create_options_tab(L("Camera"), tabs);
|
||||
m_optgroup_camera->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
|
||||
if (auto it = m_values.find(opt_key);it != m_values.end()) {
|
||||
m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected
|
||||
return;
|
||||
}
|
||||
m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0";
|
||||
};
|
||||
|
||||
@ -358,25 +383,42 @@ void PreferencesDialog::build()
|
||||
// Add "GUI" tab
|
||||
m_optgroup_gui = create_options_tab(L("GUI"), tabs);
|
||||
m_optgroup_gui->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
|
||||
if (opt_key == "suppress_hyperlinks")
|
||||
m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "";
|
||||
else if (opt_key == "notify_release") {
|
||||
if (opt_key == "notify_release") {
|
||||
int val_int = boost::any_cast<int>(value);
|
||||
for (const auto& item : s_keys_map_NotifyReleaseMode) {
|
||||
if (item.second == val_int) {
|
||||
m_values[opt_key] = item.first;
|
||||
break;
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else
|
||||
m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0";
|
||||
|
||||
}
|
||||
if (opt_key == "use_custom_toolbar_size") {
|
||||
m_icon_size_sizer->ShowItems(boost::any_cast<bool>(value));
|
||||
m_optgroup_gui->parent()->Layout();
|
||||
tabs->Layout();
|
||||
this->layout();
|
||||
refresh_og(m_optgroup_gui);
|
||||
get_app_config()->set("use_custom_toolbar_size", boost::any_cast<bool>(value) ? "1" : "0");
|
||||
get_app_config()->save();
|
||||
wxGetApp().plater()->get_current_canvas3D()->render();
|
||||
return;
|
||||
}
|
||||
if (opt_key == "tabs_as_menu") {
|
||||
bool disable_new_layout = boost::any_cast<bool>(value);
|
||||
m_rb_new_settings_layout_mode->Show(!disable_new_layout);
|
||||
if (disable_new_layout && m_rb_new_settings_layout_mode->GetValue()) {
|
||||
m_rb_new_settings_layout_mode->SetValue(false);
|
||||
m_rb_old_settings_layout_mode->SetValue(true);
|
||||
}
|
||||
refresh_og(m_optgroup_gui);
|
||||
}
|
||||
|
||||
if (auto it = m_values.find(opt_key); it != m_values.end()) {
|
||||
m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected
|
||||
return;
|
||||
}
|
||||
|
||||
if (opt_key == "suppress_hyperlinks")
|
||||
m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "";
|
||||
else
|
||||
m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0";
|
||||
};
|
||||
|
||||
append_bool_option(m_optgroup_gui, "seq_top_layer_only",
|
||||
@ -464,6 +506,10 @@ void PreferencesDialog::build()
|
||||
// Add "Render" tab
|
||||
m_optgroup_render = create_options_tab(L("Render"), tabs);
|
||||
m_optgroup_render->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
|
||||
if (auto it = m_values.find(opt_key); it != m_values.end()) {
|
||||
m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected
|
||||
return;
|
||||
}
|
||||
m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0";
|
||||
};
|
||||
|
||||
@ -479,6 +525,10 @@ void PreferencesDialog::build()
|
||||
// Add "Dark Mode" tab
|
||||
m_optgroup_dark_mode = create_options_tab(_L("Dark mode (experimental)"), tabs);
|
||||
m_optgroup_dark_mode->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
|
||||
if (auto it = m_values.find(opt_key); it != m_values.end()) {
|
||||
m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected
|
||||
return;
|
||||
}
|
||||
m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0";
|
||||
};
|
||||
|
||||
@ -509,6 +559,7 @@ void PreferencesDialog::build()
|
||||
|
||||
auto buttons = CreateStdDialogButtonSizer(wxOK | wxCANCEL);
|
||||
this->Bind(wxEVT_BUTTON, &PreferencesDialog::accept, this, wxID_OK);
|
||||
this->Bind(wxEVT_BUTTON, &PreferencesDialog::revert, this, wxID_CANCEL);
|
||||
|
||||
for (int id : {wxID_OK, wxID_CANCEL})
|
||||
wxGetApp().UpdateDarkUI(static_cast<wxButton*>(FindWindowById(id, this)));
|
||||
@ -592,19 +643,6 @@ void PreferencesDialog::accept(wxEvent&)
|
||||
}
|
||||
}
|
||||
|
||||
for (const std::string& key : { "default_action_on_close_application",
|
||||
"default_action_on_select_preset",
|
||||
"default_action_on_new_project" }) {
|
||||
auto it = m_values.find(key);
|
||||
if (it != m_values.end() && it->second != "none" && app_config->get(key) != "none")
|
||||
m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected
|
||||
}
|
||||
{
|
||||
auto it = m_values.find("default_action_on_dirty_project");
|
||||
if (it != m_values.end() && !it->second.empty() && !app_config->get("default_action_on_dirty_project").empty())
|
||||
m_values.erase(it); // we shouldn't change value, if this parameter was selected, and then deselected
|
||||
}
|
||||
|
||||
#if 0 //#ifdef _WIN32 // #ysDarkMSW - Allow it when we deside to support the sustem colors for application
|
||||
if (m_values.find("always_dark_color_mode") != m_values.end())
|
||||
wxGetApp().force_sys_colors_update();
|
||||
@ -629,11 +667,82 @@ void PreferencesDialog::accept(wxEvent&)
|
||||
wxGetApp().force_menu_update();
|
||||
#endif //_MSW_DARK_MODE
|
||||
#endif // _WIN32
|
||||
if (m_settings_layout_changed)
|
||||
;// application will be recreated after Preference dialog will be destroyed
|
||||
else
|
||||
// Nothify the UI to update itself from the ini file.
|
||||
wxGetApp().update_ui_from_settings();
|
||||
|
||||
wxGetApp().update_ui_from_settings();
|
||||
clear_cache();
|
||||
}
|
||||
|
||||
void PreferencesDialog::revert(wxEvent&)
|
||||
{
|
||||
auto app_config = get_app_config();
|
||||
|
||||
bool save_app_config = false;
|
||||
if (m_custom_toolbar_size != atoi(app_config->get("custom_toolbar_size").c_str())) {
|
||||
app_config->set("custom_toolbar_size", (boost::format("%d") % m_custom_toolbar_size).str());
|
||||
m_icon_size_slider->SetValue(m_custom_toolbar_size);
|
||||
save_app_config |= true;
|
||||
}
|
||||
if (m_use_custom_toolbar_size != (get_app_config()->get("use_custom_toolbar_size") == "1")) {
|
||||
app_config->set("use_custom_toolbar_size", m_use_custom_toolbar_size ? "1" : "0");
|
||||
save_app_config |= true;
|
||||
|
||||
m_optgroup_gui->set_value("use_custom_toolbar_size", m_use_custom_toolbar_size);
|
||||
m_icon_size_sizer->ShowItems(m_use_custom_toolbar_size);
|
||||
refresh_og(m_optgroup_gui);
|
||||
}
|
||||
if (save_app_config)
|
||||
app_config->save();
|
||||
|
||||
|
||||
for (auto value : m_values) {
|
||||
bool reverted = false;
|
||||
const std::string& key = value.first;
|
||||
|
||||
if (key == "default_action_on_dirty_project") {
|
||||
m_optgroup_general->set_value(key, app_config->get(key).empty());
|
||||
continue;
|
||||
}
|
||||
if (key == "default_action_on_close_application" || key == "default_action_on_select_preset" || key == "default_action_on_new_project") {
|
||||
m_optgroup_general->set_value(key, app_config->get(key) == "none");
|
||||
continue;
|
||||
}
|
||||
if (key == "notify_release") {
|
||||
m_optgroup_gui->set_value(key, s_keys_map_NotifyReleaseMode.at(app_config->get(key)));
|
||||
continue;
|
||||
}
|
||||
if (key == "old_settings_layout_mode") {
|
||||
m_rb_old_settings_layout_mode->SetValue(app_config->get(key) == "1");
|
||||
continue;
|
||||
}
|
||||
if (key == "new_settings_layout_mode") {
|
||||
m_rb_new_settings_layout_mode->SetValue(app_config->get(key) == "1");
|
||||
continue;
|
||||
}
|
||||
if (key == "dlg_settings_layout_mode") {
|
||||
m_rb_dlg_settings_layout_mode->SetValue(app_config->get(key) == "1");
|
||||
continue;
|
||||
}
|
||||
|
||||
for (auto opt_group : { m_optgroup_general, m_optgroup_camera, m_optgroup_gui
|
||||
#ifdef _WIN32
|
||||
, m_optgroup_dark_mode
|
||||
#endif // _WIN32
|
||||
#if ENABLE_ENVIRONMENT_MAP
|
||||
, m_optgroup_render
|
||||
#endif // ENABLE_ENVIRONMENT_MAP
|
||||
}) {
|
||||
if (opt_group->set_value(key, app_config->get(key) == "1"))
|
||||
break;
|
||||
}
|
||||
if (key == "tabs_as_menu") {
|
||||
m_rb_new_settings_layout_mode->Show(app_config->get(key) != "1");
|
||||
refresh_og(m_optgroup_gui);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
clear_cache();
|
||||
EndModal(wxID_CANCEL);
|
||||
}
|
||||
|
||||
void PreferencesDialog::msw_rescale()
|
||||
@ -669,6 +778,19 @@ void PreferencesDialog::layout()
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void PreferencesDialog::clear_cache()
|
||||
{
|
||||
m_values.clear();
|
||||
m_custom_toolbar_size = -1;
|
||||
}
|
||||
|
||||
void PreferencesDialog::refresh_og(std::shared_ptr<ConfigOptionsGroup> og)
|
||||
{
|
||||
og->parent()->Layout();
|
||||
tabs->Layout();
|
||||
this->layout();
|
||||
}
|
||||
|
||||
void PreferencesDialog::create_icon_size_slider()
|
||||
{
|
||||
const auto app_config = get_app_config();
|
||||
@ -695,14 +817,14 @@ void PreferencesDialog::create_icon_size_slider()
|
||||
if (!isOSX)
|
||||
style |= wxSL_LABELS | wxSL_AUTOTICKS;
|
||||
|
||||
auto slider = new wxSlider(parent, wxID_ANY, def_val, 30, 100,
|
||||
m_icon_size_slider = new wxSlider(parent, wxID_ANY, def_val, 30, 100,
|
||||
wxDefaultPosition, wxDefaultSize, style);
|
||||
|
||||
slider->SetTickFreq(10);
|
||||
slider->SetPageSize(10);
|
||||
slider->SetToolTip(_L("Select toolbar icon size in respect to the default one."));
|
||||
m_icon_size_slider->SetTickFreq(10);
|
||||
m_icon_size_slider->SetPageSize(10);
|
||||
m_icon_size_slider->SetToolTip(_L("Select toolbar icon size in respect to the default one."));
|
||||
|
||||
m_icon_size_sizer->Add(slider, 1, wxEXPAND);
|
||||
m_icon_size_sizer->Add(m_icon_size_slider, 1, wxEXPAND);
|
||||
|
||||
wxStaticText* val_label{ nullptr };
|
||||
if (isOSX) {
|
||||
@ -710,15 +832,18 @@ void PreferencesDialog::create_icon_size_slider()
|
||||
m_icon_size_sizer->Add(val_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, em);
|
||||
}
|
||||
|
||||
slider->Bind(wxEVT_SLIDER, ([this, slider, val_label](wxCommandEvent e) {
|
||||
auto val = slider->GetValue();
|
||||
m_values["custom_toolbar_size"] = (boost::format("%d") % val).str();
|
||||
m_icon_size_slider->Bind(wxEVT_SLIDER, ([this, val_label, app_config](wxCommandEvent e) {
|
||||
auto val = m_icon_size_slider->GetValue();
|
||||
|
||||
app_config->set("custom_toolbar_size", (boost::format("%d") % val).str());
|
||||
app_config->save();
|
||||
wxGetApp().plater()->get_current_canvas3D()->render();
|
||||
|
||||
if (val_label)
|
||||
val_label->SetLabelText(wxString::Format("%d", val));
|
||||
}), slider->GetId());
|
||||
}), m_icon_size_slider->GetId());
|
||||
|
||||
for (wxWindow* win : std::vector<wxWindow*>{ slider, label, val_label }) {
|
||||
for (wxWindow* win : std::vector<wxWindow*>{ m_icon_size_slider, label, val_label }) {
|
||||
if (!win) continue;
|
||||
win->SetFont(wxGetApp().normal_font());
|
||||
|
||||
@ -731,26 +856,6 @@ void PreferencesDialog::create_icon_size_slider()
|
||||
|
||||
void PreferencesDialog::create_settings_mode_widget()
|
||||
{
|
||||
#ifdef _MSW_DARK_MODE
|
||||
bool disable_new_layout = wxGetApp().tabs_as_menu();
|
||||
#endif
|
||||
std::vector<wxString> choices = { _L("Old regular layout with the tab bar"),
|
||||
_L("New layout, access via settings button in the top menu"),
|
||||
_L("Settings in non-modal window") };
|
||||
|
||||
auto app_config = get_app_config();
|
||||
int selection = app_config->get("old_settings_layout_mode") == "1" ? 0 :
|
||||
app_config->get("new_settings_layout_mode") == "1" ? 1 :
|
||||
app_config->get("dlg_settings_layout_mode") == "1" ? 2 : 0;
|
||||
|
||||
#ifdef _MSW_DARK_MODE
|
||||
if (disable_new_layout) {
|
||||
choices = { _L("Old regular layout with the tab bar"),
|
||||
_L("Settings in non-modal window") };
|
||||
selection = app_config->get("dlg_settings_layout_mode") == "1" ? 1 : 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
wxWindow* parent = m_optgroup_gui->parent();
|
||||
wxGetApp().UpdateDarkUI(parent);
|
||||
|
||||
@ -762,32 +867,35 @@ void PreferencesDialog::create_settings_mode_widget()
|
||||
|
||||
wxSizer* stb_sizer = new wxStaticBoxSizer(stb, wxVERTICAL);
|
||||
|
||||
int id = 0;
|
||||
for (const wxString& label : choices) {
|
||||
wxRadioButton* btn = new wxRadioButton(parent, wxID_ANY, label, wxDefaultPosition, wxDefaultSize, id==0 ? wxRB_GROUP : 0);
|
||||
stb_sizer->Add(btn);
|
||||
btn->SetValue(id == selection);
|
||||
|
||||
int dlg_id = 2;
|
||||
#ifdef _MSW_DARK_MODE
|
||||
if (disable_new_layout)
|
||||
dlg_id = 1;
|
||||
#endif
|
||||
|
||||
btn->Bind(wxEVT_RADIOBUTTON, [this, id, dlg_id
|
||||
#ifdef _MSW_DARK_MODE
|
||||
, disable_new_layout
|
||||
#endif
|
||||
](wxCommandEvent& ) {
|
||||
m_values["old_settings_layout_mode"] = (id == 0) ? "1" : "0";
|
||||
#ifdef _MSW_DARK_MODE
|
||||
if (!disable_new_layout)
|
||||
#endif
|
||||
m_values["new_settings_layout_mode"] = (id == 1) ? "1" : "0";
|
||||
m_values["dlg_settings_layout_mode"] = (id == dlg_id) ? "1" : "0";
|
||||
auto app_config = get_app_config();
|
||||
std::vector<wxString> choices = { _L("Old regular layout with the tab bar"),
|
||||
_L("New layout, access via settings button in the top menu"),
|
||||
_L("Settings in non-modal window") };
|
||||
int id = -1;
|
||||
auto add_radio = [this, parent, stb_sizer, choices](wxRadioButton** rb, int id, bool select) {
|
||||
*rb = new wxRadioButton(parent, wxID_ANY, choices[id], wxDefaultPosition, wxDefaultSize, id == 0 ? wxRB_GROUP : 0);
|
||||
stb_sizer->Add(*rb);
|
||||
(*rb)->SetValue(select);
|
||||
(*rb)->Bind(wxEVT_RADIOBUTTON, [this, id](wxCommandEvent&) {
|
||||
m_values["old_settings_layout_mode"] = (id == 0) ? "1" : "0";
|
||||
m_values["new_settings_layout_mode"] = (id == 1) ? "1" : "0";
|
||||
m_values["dlg_settings_layout_mode"] = (id == 2) ? "1" : "0";
|
||||
});
|
||||
id++;
|
||||
};
|
||||
|
||||
add_radio(&m_rb_old_settings_layout_mode, ++id, app_config->get("old_settings_layout_mode") == "1");
|
||||
add_radio(&m_rb_new_settings_layout_mode, ++id, app_config->get("new_settings_layout_mode") == "1");
|
||||
add_radio(&m_rb_dlg_settings_layout_mode, ++id, app_config->get("dlg_settings_layout_mode") == "1");
|
||||
|
||||
#ifdef _MSW_DARK_MODE
|
||||
if (app_config->get("tabs_as_menu") == "1") {
|
||||
m_rb_new_settings_layout_mode->Hide();
|
||||
if (m_rb_new_settings_layout_mode->GetValue()) {
|
||||
m_rb_new_settings_layout_mode->SetValue(false);
|
||||
m_rb_old_settings_layout_mode->SetValue(true);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string opt_key = "settings_layout_mode";
|
||||
m_blinkers[opt_key] = new BlinkingBitmap(parent);
|
||||
|
@ -12,6 +12,8 @@
|
||||
|
||||
class wxColourPickerCtrl;
|
||||
class wxBookCtrlBase;
|
||||
class wxSlider;
|
||||
class wxRadioButton;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@ -39,6 +41,11 @@ class PreferencesDialog : public DPIDialog
|
||||
std::shared_ptr<ConfigOptionsGroup> m_optgroup_render;
|
||||
#endif // ENABLE_ENVIRONMENT_MAP
|
||||
wxSizer* m_icon_size_sizer;
|
||||
wxSlider* m_icon_size_slider {nullptr};
|
||||
wxRadioButton* m_rb_old_settings_layout_mode {nullptr};
|
||||
wxRadioButton* m_rb_new_settings_layout_mode {nullptr};
|
||||
wxRadioButton* m_rb_dlg_settings_layout_mode {nullptr};
|
||||
|
||||
wxColourPickerCtrl* m_sys_colour {nullptr};
|
||||
wxColourPickerCtrl* m_mod_colour {nullptr};
|
||||
wxBookCtrlBase* tabs {nullptr};
|
||||
@ -48,6 +55,9 @@ class PreferencesDialog : public DPIDialog
|
||||
bool m_seq_top_layer_only_changed{ false };
|
||||
bool m_recreate_GUI{false};
|
||||
|
||||
int m_custom_toolbar_size{-1};
|
||||
bool m_use_custom_toolbar_size{false};
|
||||
|
||||
public:
|
||||
explicit PreferencesDialog(wxWindow* paren);
|
||||
~PreferencesDialog() = default;
|
||||
@ -58,6 +68,7 @@ public:
|
||||
void build();
|
||||
void update_ctrls_alignment();
|
||||
void accept(wxEvent&);
|
||||
void revert(wxEvent&);
|
||||
void show(const std::string& highlight_option = std::string(), const std::string& tab_name = std::string());
|
||||
|
||||
protected:
|
||||
@ -65,6 +76,8 @@ protected:
|
||||
void on_dpi_changed(const wxRect& suggested_rect) override { msw_rescale(); }
|
||||
void on_sys_color_changed() override;
|
||||
void layout();
|
||||
void clear_cache();
|
||||
void refresh_og(std::shared_ptr<ConfigOptionsGroup> og);
|
||||
void create_icon_size_slider();
|
||||
void create_settings_mode_widget();
|
||||
void create_settings_text_color_widget();
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include <boost/nowide/convert.hpp>
|
||||
#include <boost/property_tree/ini_parser.hpp>
|
||||
#include <curl/curl.h>
|
||||
@ -242,7 +243,7 @@ boost::filesystem::path AppUpdater::priv::download_file(const DownloadAppData& d
|
||||
tmp_path += format(".%1%%2%", get_current_pid(), ".download");
|
||||
try
|
||||
{
|
||||
boost::filesystem::fstream file(tmp_path, std::ios::out | std::ios::binary | std::ios::trunc);
|
||||
boost::nowide::fstream file(tmp_path.string(), std::ios::out | std::ios::binary | std::ios::trunc);
|
||||
file.write(body.c_str(), body.size());
|
||||
file.close();
|
||||
boost::filesystem::rename(tmp_path, dest_path);
|
||||
|
@ -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__
|
210
t/custom_gcode.t
210
t/custom_gcode.t
@ -1,210 +0,0 @@
|
||||
use Test::More tests => 38;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
BEGIN {
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
use local::lib "$FindBin::Bin/../local-lib";
|
||||
}
|
||||
|
||||
use List::Util qw(first);
|
||||
use Slic3r;
|
||||
use Slic3r::Test;
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config::new_from_defaults;
|
||||
|
||||
my $test = sub {
|
||||
my ($conf) = @_;
|
||||
$conf ||= $config;
|
||||
|
||||
my $print = Slic3r::Test::init_print('2x20x10', config => $conf);
|
||||
|
||||
my $last_move_was_z_change = 0;
|
||||
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
|
||||
my ($self, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($last_move_was_z_change && $cmd ne $config->layer_gcode) {
|
||||
fail 'custom layer G-code was not applied after Z change';
|
||||
}
|
||||
if (!$last_move_was_z_change && $cmd eq $config->layer_gcode) {
|
||||
fail 'custom layer G-code was not applied after Z change';
|
||||
}
|
||||
|
||||
$last_move_was_z_change = (defined $info->{dist_Z} && $info->{dist_Z} > 0);
|
||||
});
|
||||
|
||||
1;
|
||||
};
|
||||
|
||||
$config->set('start_gcode', '_MY_CUSTOM_START_GCODE_'); # to avoid dealing with the nozzle lift in start G-code
|
||||
$config->set('layer_gcode', '_MY_CUSTOM_LAYER_GCODE_');
|
||||
ok $test->(), "custom layer G-code is applied after Z move and before other moves";
|
||||
}
|
||||
|
||||
#==========================================================
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config::new_from_defaults;
|
||||
$config->set('output_filename_format', 'ts_[travel_speed]_lh_[layer_height].gcode');
|
||||
$config->set('start_gcode', "TRAVEL:[travel_speed] HEIGHT:[layer_height]\n");
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
||||
|
||||
my $output_file = $print->print->output_filepath;
|
||||
my ($t, $h) = map $config->$_, qw(travel_speed layer_height);
|
||||
ok $output_file =~ /ts_${t}_/, 'print config options are replaced in output filename';
|
||||
ok $output_file =~ /lh_$h\./, 'region config options are replaced in output filename';
|
||||
|
||||
my $gcode = Slic3r::Test::gcode($print);
|
||||
ok $gcode =~ /TRAVEL:$t/, 'print config options are replaced in custom G-code';
|
||||
ok $gcode =~ /HEIGHT:$h/, 'region config options are replaced in custom G-code';
|
||||
}
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config->new;
|
||||
$config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]);
|
||||
$config->set('extruder', 2);
|
||||
$config->set('first_layer_temperature', [200,205]);
|
||||
|
||||
{
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
||||
my $gcode = Slic3r::Test::gcode($print);
|
||||
ok $gcode =~ /M104 S205 T1/, 'temperature set correctly for non-zero yet single extruder';
|
||||
ok $gcode !~ /M104 S\d+ T0/, 'unused extruder correctly ignored';
|
||||
}
|
||||
|
||||
$config->set('infill_extruder', 1);
|
||||
{
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
||||
my $gcode = Slic3r::Test::gcode($print);
|
||||
ok $gcode =~ /M104 S200 T0/, 'temperature set correctly for first extruder';
|
||||
ok $gcode =~ /M104 S205 T1/, 'temperature set correctly for second extruder';
|
||||
}
|
||||
|
||||
my @start_gcode = (qq!
|
||||
;__temp0:[first_layer_temperature_0]__
|
||||
;__temp1:[first_layer_temperature_1]__
|
||||
;__temp2:[first_layer_temperature_2]__
|
||||
!, qq!
|
||||
;__temp0:{first_layer_temperature[0]}__
|
||||
;__temp1:{first_layer_temperature[1]}__
|
||||
;__temp2:{first_layer_temperature[2]}__
|
||||
!);
|
||||
my @syntax_description = (' (legacy syntax)', ' (new syntax)');
|
||||
for my $i (0, 1) {
|
||||
$config->set('start_gcode', $start_gcode[$i]);
|
||||
{
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
||||
my $gcode = Slic3r::Test::gcode($print);
|
||||
# we use the [infill_extruder] placeholder to make sure this test doesn't
|
||||
# catch a false positive caused by the unparsed start G-code option itself
|
||||
# being embedded in the G-code
|
||||
ok $gcode =~ /temp0:200/, 'temperature placeholder for first extruder correctly populated' . $syntax_description[$i];
|
||||
ok $gcode =~ /temp1:205/, 'temperature placeholder for second extruder correctly populated' . $syntax_description[$i];
|
||||
ok $gcode =~ /temp2:200/, 'temperature placeholder for unused extruder populated with first value' . $syntax_description[$i];
|
||||
}
|
||||
}
|
||||
|
||||
$config->set('start_gcode', qq!
|
||||
;substitution:{if infill_extruder==1}extruder1
|
||||
{elsif infill_extruder==2}extruder2
|
||||
{else}extruder3{endif}
|
||||
!);
|
||||
{
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
||||
my $gcode = Slic3r::Test::gcode($print);
|
||||
ok $gcode =~ /substitution:extruder1/, 'if / else / endif - first block returned';
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config::new_from_defaults;
|
||||
$config->set('before_layer_gcode', ';BEFORE [layer_num]');
|
||||
$config->set('layer_gcode', ';CHANGE [layer_num]');
|
||||
$config->set('support_material', 1);
|
||||
$config->set('layer_height', 0.2);
|
||||
my $print = Slic3r::Test::init_print('overhang', config => $config);
|
||||
my $gcode = Slic3r::Test::gcode($print);
|
||||
|
||||
my @before = ();
|
||||
my @change = ();
|
||||
foreach my $line (split /\R+/, $gcode) {
|
||||
if ($line =~ /;BEFORE (\d+)/) {
|
||||
push @before, $1;
|
||||
} elsif ($line =~ /;CHANGE (\d+)/) {
|
||||
push @change, $1;
|
||||
fail 'inconsistent layer_num before and after layer change'
|
||||
if $1 != $before[-1];
|
||||
}
|
||||
}
|
||||
is_deeply \@before, \@change, 'layer_num is consistent before and after layer changes';
|
||||
ok !defined(first { $change[$_] != $change[$_-1]+1 } 1..$#change),
|
||||
'layer_num grows continously'; # i.e. no duplicates or regressions
|
||||
}
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config->new;
|
||||
$config->set('nozzle_diameter', [0.6,0.6,0.6,0.6,0.6]);
|
||||
$config->set('start_gcode', qq!
|
||||
;substitution:{if infill_extruder==1}if block
|
||||
{elsif infill_extruder==2}elsif block 1
|
||||
{elsif infill_extruder==3}elsif block 2
|
||||
{elsif infill_extruder==4}elsif block 3
|
||||
{else}endif block{endif}
|
||||
!);
|
||||
my @returned = ('', 'if block', 'elsif block 1', 'elsif block 2', 'elsif block 3', 'endif block');
|
||||
for my $i (1,2,3,4,5) {
|
||||
$config->set('infill_extruder', $i);
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
||||
my $gcode = Slic3r::Test::gcode($print);
|
||||
my $found_other = 0;
|
||||
for my $j (1,2,3,4,5) {
|
||||
next if $i == $j;
|
||||
$found_other = 1 if $gcode =~ /substitution:$returned[$j]/;
|
||||
}
|
||||
ok $gcode =~ /substitution:$returned[$i]/, 'if / else / endif - ' . $returned[$i] . ' returned';
|
||||
ok !$found_other, 'if / else / endif - only ' . $returned[$i] . ' returned';
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config->new;
|
||||
$config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]);
|
||||
$config->set('start_gcode',
|
||||
';substitution:{if infill_extruder==1}{if perimeter_extruder==1}block11{else}block12{endif}' .
|
||||
'{elsif infill_extruder==2}{if perimeter_extruder==1}block21{else}block22{endif}' .
|
||||
'{else}{if perimeter_extruder==1}block31{else}block32{endif}{endif}:end');
|
||||
for my $i (1,2,3) {
|
||||
$config->set('infill_extruder', $i);
|
||||
for my $j (1,2) {
|
||||
$config->set('perimeter_extruder', $j);
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
||||
my $gcode = Slic3r::Test::gcode($print);
|
||||
ok $gcode =~ /substitution:block$i$j:end/, "two level if / else / endif - block$i$j returned";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config->new;
|
||||
$config->set('start_gcode',
|
||||
';substitution:{if notes=="MK2"}MK2{elsif notes=="MK3"}MK3{else}MK1{endif}:end');
|
||||
for my $printer_name ("MK2", "MK3", "MK1") {
|
||||
$config->set('notes', $printer_name);
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
||||
my $gcode = Slic3r::Test::gcode($print);
|
||||
ok $gcode =~ /substitution:$printer_name:end/, "printer name $printer_name matched";
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config::new_from_defaults;
|
||||
$config->set('complete_objects', 1);
|
||||
$config->set('between_objects_gcode', '_MY_CUSTOM_GCODE_');
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 3);
|
||||
my $gcode = Slic3r::Test::gcode($print);
|
||||
is scalar(() = $gcode =~ /^_MY_CUSTOM_GCODE_/gm), 2, 'between_objects_gcode is applied correctly';
|
||||
}
|
||||
|
||||
__END__
|
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__
|
65
t/print.t
65
t/print.t
@ -1,65 +0,0 @@
|
||||
use Test::More tests => 6;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
BEGIN {
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
use local::lib "$FindBin::Bin/../local-lib";
|
||||
}
|
||||
|
||||
use List::Util qw(first);
|
||||
use Slic3r;
|
||||
use Slic3r::Geometry qw(unscale X Y);
|
||||
use Slic3r::Test;
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config::new_from_defaults;
|
||||
my $print_center = [100,100];
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config, print_center => $print_center);
|
||||
my @extrusion_points = ();
|
||||
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
|
||||
my ($self, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) {
|
||||
push @extrusion_points, my $point = Slic3r::Point->new_scale($args->{X}, $args->{Y});
|
||||
}
|
||||
});
|
||||
my $bb = Slic3r::Geometry::BoundingBox->new_from_points(\@extrusion_points);
|
||||
my $center = $bb->center;
|
||||
ok abs(unscale($center->[X]) - $print_center->[X]) < 0.005, 'print is centered around print_center (X)';
|
||||
ok abs(unscale($center->[Y]) - $print_center->[Y]) < 0.005, 'print is centered around print_center (Y)';
|
||||
}
|
||||
|
||||
{
|
||||
# this represents the aggregate config from presets
|
||||
my $config = Slic3r::Config::new_from_defaults;
|
||||
# Define 4 extruders.
|
||||
$config->set('nozzle_diameter', [0.4, 0.4, 0.4, 0.4]);
|
||||
|
||||
# user adds one object to the plater
|
||||
my $print = Slic3r::Test::init_print(my $model = Slic3r::Test::model('20mm_cube'), config => $config);
|
||||
|
||||
# user sets a per-region option
|
||||
my $model2 = $model->clone;
|
||||
$model2->get_object(0)->config->set('fill_density', 100);
|
||||
$print->apply($model2, $config);
|
||||
|
||||
is $print->print->regions->[0]->config->fill_density, 100, 'region config inherits model object config';
|
||||
|
||||
# user exports G-code, thus the default config is reapplied
|
||||
$model2->get_object(0)->config->erase('fill_density');
|
||||
$print->apply($model2, $config);
|
||||
|
||||
is $print->print->regions->[0]->config->fill_density, 20, 'region config is resetted';
|
||||
|
||||
# user assigns object extruders
|
||||
$model2->get_object(0)->config->set('extruder', 3);
|
||||
$model2->get_object(0)->config->set('perimeter_extruder', 2);
|
||||
$print->apply($model2, $config);
|
||||
|
||||
is $print->print->regions->[0]->config->infill_extruder, 3, 'extruder setting is correctly expanded';
|
||||
is $print->print->regions->[0]->config->perimeter_extruder, 2, 'extruder setting does not override explicitely specified extruders';
|
||||
}
|
||||
|
||||
__END__
|
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, '';
|
||||
}
|
||||
}
|
||||
}
|
261
tests/fff_print/test_custom_gcode.cpp
Normal file
261
tests/fff_print/test_custom_gcode.cpp
Normal file
@ -0,0 +1,261 @@
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include <exception>
|
||||
#include <numeric>
|
||||
#include <sstream>
|
||||
|
||||
#include <boost/regex.hpp>
|
||||
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
#include "test_data.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
SCENARIO("Output file format", "[CustomGCode]")
|
||||
{
|
||||
WHEN("output_file_format set") {
|
||||
auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
|
||||
{ "travel_speed", "130"},
|
||||
{ "layer_height", "0.4"},
|
||||
{ "output_filename_format", "ts_[travel_speed]_lh_[layer_height].gcode" },
|
||||
{ "start_gcode", "TRAVEL:[travel_speed] HEIGHT:[layer_height]\n" }
|
||||
});
|
||||
|
||||
Print print;
|
||||
Model model;
|
||||
Test::init_print({ Test::TestMesh::cube_2x20x10 }, print, model, config);
|
||||
|
||||
std::string output_file = print.output_filepath({}, {});
|
||||
THEN("print config options are replaced in output filename") {
|
||||
REQUIRE(output_file == "ts_130_lh_0.4.gcode");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("Custom G-code", "[CustomGCode]")
|
||||
{
|
||||
WHEN("start_gcode and layer_gcode set") {
|
||||
auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
|
||||
{ "start_gcode", "_MY_CUSTOM_START_GCODE_" }, // to avoid dealing with the nozzle lift in start G-code
|
||||
{ "layer_gcode", "_MY_CUSTOM_LAYER_GCODE_" }
|
||||
});
|
||||
GCodeReader parser;
|
||||
bool last_move_was_z_change = false;
|
||||
int num_layer_changes_not_applied = 0;
|
||||
parser.parse_buffer(Slic3r::Test::slice({ Test::TestMesh::cube_2x20x10 }, config),
|
||||
[&last_move_was_z_change, &num_layer_changes_not_applied](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line)
|
||||
{
|
||||
if (last_move_was_z_change != line.cmd_is("_MY_CUSTOM_LAYER_GCODE_"))
|
||||
++ num_layer_changes_not_applied;
|
||||
last_move_was_z_change = line.dist_Z(self) > 0;
|
||||
});
|
||||
THEN("custom layer G-code is applied after Z move and before other moves") {
|
||||
REQUIRE(num_layer_changes_not_applied == 0);
|
||||
}
|
||||
};
|
||||
|
||||
auto config = Slic3r::DynamicPrintConfig::new_with({
|
||||
{ "nozzle_diameter", { 0.6,0.6,0.6,0.6 } },
|
||||
{ "extruder", 2 },
|
||||
{ "first_layer_temperature", { 200, 205 } }
|
||||
});
|
||||
config.normalize_fdm();
|
||||
WHEN("Printing with single but non-zero extruder") {
|
||||
std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config);
|
||||
THEN("temperature set correctly for non-zero yet single extruder") {
|
||||
REQUIRE(Slic3r::Test::contains(gcode, "\nM104 S205 T1 ;"));
|
||||
}
|
||||
THEN("unused extruder correctly ignored") {
|
||||
REQUIRE(! Slic3r::Test::contains_regex(gcode, "M104 S\\d+ T0"));
|
||||
}
|
||||
}
|
||||
WHEN("Printing with two extruders") {
|
||||
config.opt_int("infill_extruder") = 1;
|
||||
std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config);
|
||||
THEN("temperature set correctly for first extruder") {
|
||||
REQUIRE(Slic3r::Test::contains(gcode, "\nM104 S200 T0 ;"));
|
||||
};
|
||||
THEN("temperature set correctly for second extruder") {
|
||||
REQUIRE(Slic3r::Test::contains(gcode, "\nM104 S205 T1 ;"));
|
||||
};
|
||||
}
|
||||
|
||||
auto test = [](DynamicPrintConfig &config) {
|
||||
// we use the [infill_extruder] placeholder to make sure this test doesn't
|
||||
// catch a false positive caused by the unparsed start G-code option itself
|
||||
// being embedded in the G-code
|
||||
config.opt_int("infill_extruder") = 1;
|
||||
std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config);
|
||||
THEN("temperature placeholder for first extruder correctly populated") {
|
||||
REQUIRE(Slic3r::Test::contains(gcode, "temp0:200"));
|
||||
}
|
||||
THEN("temperature placeholder for second extruder correctly populated") {
|
||||
REQUIRE(Slic3r::Test::contains(gcode, "temp1:205"));
|
||||
}
|
||||
THEN("temperature placeholder for unused extruder populated with first value") {
|
||||
REQUIRE(Slic3r::Test::contains(gcode, "temp2:200"));
|
||||
}
|
||||
};
|
||||
WHEN("legacy syntax") {
|
||||
config.set_deserialize_strict("start_gcode",
|
||||
";__temp0:[first_layer_temperature_0]__\n"
|
||||
";__temp1:[first_layer_temperature_1]__\n"
|
||||
";__temp2:[first_layer_temperature_2]__\n");
|
||||
test(config);
|
||||
}
|
||||
WHEN("new syntax") {
|
||||
config.set_deserialize_strict("start_gcode",
|
||||
";__temp0:{first_layer_temperature[0]}__\n"
|
||||
";__temp1:{first_layer_temperature[1]}__\n"
|
||||
";__temp2:{first_layer_temperature[2]}__\n");
|
||||
test(config);
|
||||
}
|
||||
WHEN("Vojtech's syntax") {
|
||||
config.set_deserialize_strict({
|
||||
{ "infill_extruder", 1 },
|
||||
{ "start_gcode",
|
||||
";substitution:{if infill_extruder==1}extruder1"
|
||||
"{elsif infill_extruder==2}extruder2"
|
||||
"{else}extruder3{endif}"
|
||||
}
|
||||
});
|
||||
std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config);
|
||||
THEN("if / else / endif - first block returned") {
|
||||
REQUIRE(Test::contains(gcode, "\n;substitution:extruder1\n"));
|
||||
}
|
||||
}
|
||||
GIVEN("Layer change G-codes")
|
||||
{
|
||||
auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
|
||||
{ "before_layer_gcode", ";BEFORE [layer_num]" },
|
||||
{ "layer_gcode", ";CHANGE [layer_num]" },
|
||||
{ "support_material", 1 },
|
||||
{ "layer_height", 0.2 }
|
||||
});
|
||||
WHEN("before and after layer change G-codes set") {
|
||||
std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::overhang }, config);
|
||||
GCodeReader parser;
|
||||
std::vector<int> before;
|
||||
std::vector<int> change;
|
||||
parser.parse_buffer(gcode, [&before, &change](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line){
|
||||
int d;
|
||||
if (sscanf(line.raw().c_str(), ";BEFORE %d", &d) == 1)
|
||||
before.emplace_back(d);
|
||||
else if (sscanf(line.raw().c_str(), ";CHANGE %d", &d) == 1) {
|
||||
change.emplace_back(d);
|
||||
if (d != before.back())
|
||||
throw std::runtime_error("inconsistent layer_num before and after layer change");
|
||||
}
|
||||
});
|
||||
THEN("layer_num is consistent before and after layer changes") {
|
||||
REQUIRE(before == change);
|
||||
}
|
||||
THEN("layer_num grows continously") {
|
||||
// i.e. no duplicates or regressions
|
||||
bool successive = true;
|
||||
for (size_t i = 1; i < change.size(); ++ i)
|
||||
if (change[i - 1] + 1 != change[i])
|
||||
successive = false;
|
||||
REQUIRE(successive);
|
||||
}
|
||||
}
|
||||
}
|
||||
GIVEN("if / elsif / elsif / elsif / else / endif")
|
||||
{
|
||||
auto config = Slic3r::DynamicPrintConfig::new_with({
|
||||
{ "nozzle_diameter", { 0.6,0.6,0.6,0.6,0.6 } },
|
||||
{ "start_gcode",
|
||||
";substitution:{if infill_extruder==1}if block"
|
||||
"{elsif infill_extruder==2}elsif block 1"
|
||||
"{elsif infill_extruder==3}elsif block 2"
|
||||
"{elsif infill_extruder==4}elsif block 3"
|
||||
"{else}endif block{endif}"
|
||||
":end"
|
||||
}
|
||||
});
|
||||
std::string returned[] = { "" /* indexed by one based extruder ID */, "if block", "elsif block 1", "elsif block 2", "elsif block 3", "endif block" };
|
||||
auto test = [&config, &returned](int i) {
|
||||
config.set_deserialize_strict({ { "infill_extruder", i } });
|
||||
std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config);
|
||||
int found_error = 0;
|
||||
for (int j = 1; j <= 5; ++ j)
|
||||
if (i != j && Slic3r::Test::contains(gcode, std::string("substitution:") + returned[j] + ":end"))
|
||||
// failure
|
||||
++ found_error;
|
||||
THEN(std::string("if / else / endif returned ") + returned[i]) {
|
||||
REQUIRE(Slic3r::Test::contains(gcode, std::string("substitution:") + returned[i] + ":end"));
|
||||
}
|
||||
THEN(std::string("if / else / endif - only ") + returned[i] + "returned") {
|
||||
REQUIRE(found_error == 0);
|
||||
}
|
||||
};
|
||||
WHEN("infill_extruder == 1") { test(1); }
|
||||
WHEN("infill_extruder == 2") { test(2); }
|
||||
WHEN("infill_extruder == 3") { test(3); }
|
||||
WHEN("infill_extruder == 4") { test(4); }
|
||||
WHEN("infill_extruder == 5") { test(5); }
|
||||
}
|
||||
GIVEN("nested if / if / else / endif") {
|
||||
auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
|
||||
{ "nozzle_diameter", { 0.6,0.6,0.6,0.6,0.6 } },
|
||||
{ "start_gcode",
|
||||
";substitution:{if infill_extruder==1}{if perimeter_extruder==1}block11{else}block12{endif}"
|
||||
"{elsif infill_extruder==2}{if perimeter_extruder==1}block21{else}block22{endif}"
|
||||
"{else}{if perimeter_extruder==1}block31{else}block32{endif}{endif}:end"
|
||||
}
|
||||
});
|
||||
auto test = [&config](int i) {
|
||||
config.opt_int("infill_extruder") = i;
|
||||
int failed = 0;
|
||||
for (int j = 1; j <= 2; ++ j) {
|
||||
config.opt_int("perimeter_extruder") = j;
|
||||
std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config);
|
||||
if (! Slic3r::Test::contains(gcode, std::string("substitution:block") + std::to_string(i) + std::to_string(j) + ":end"))
|
||||
++ failed;
|
||||
}
|
||||
THEN(std::string("two level if / else / endif - block for infill_extruder ") + std::to_string(i) + "succeeded") {
|
||||
REQUIRE(failed == 0);
|
||||
}
|
||||
};
|
||||
WHEN("infill_extruder == 1") { test(1); }
|
||||
WHEN("infill_extruder == 2") { test(2); }
|
||||
WHEN("infill_extruder == 3") { test(3); }
|
||||
}
|
||||
GIVEN("printer type in notes") {
|
||||
auto config = Slic3r::DynamicPrintConfig::new_with({
|
||||
{ "start_gcode",
|
||||
";substitution:{if notes==\"MK2\"}MK2{elsif notes==\"MK3\"}MK3{else}MK1{endif}:end"
|
||||
}
|
||||
});
|
||||
auto test = [&config](const std::string &printer_name) {
|
||||
config.set_deserialize_strict("notes", printer_name);
|
||||
std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config);
|
||||
THEN(std::string("printer name ") + printer_name + " matched") {
|
||||
REQUIRE(Slic3r::Test::contains(gcode, std::string("substitution:") + printer_name + ":end"));
|
||||
}
|
||||
};
|
||||
WHEN("printer MK2") { test("MK2"); }
|
||||
WHEN("printer MK3") { test("MK3"); }
|
||||
WHEN("printer MK1") { test("MK1"); }
|
||||
}
|
||||
GIVEN("sequential print with between_objects_gcode") {
|
||||
auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
|
||||
{ "complete_objects", 1 },
|
||||
{ "between_objects_gcode", "_MY_CUSTOM_GCODE_" }
|
||||
});
|
||||
std::string gcode = Slic3r::Test::slice(
|
||||
// 3x 20mm box
|
||||
{ Slic3r::Test::TestMesh::cube_20x20x20, Slic3r::Test::TestMesh::cube_20x20x20, Slic3r::Test::TestMesh::cube_20x20x20 },
|
||||
config);
|
||||
THEN("between_objects_gcode is applied correctly") {
|
||||
const boost::regex expression("^_MY_CUSTOM_GCODE_");
|
||||
const std::ptrdiff_t match_count =
|
||||
std::distance(boost::sregex_iterator(gcode.begin(), gcode.end(), expression), boost::sregex_iterator());
|
||||
REQUIRE(match_count == 2);
|
||||
}
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@
|
||||
#include <boost/nowide/cstdio.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/regex.hpp>
|
||||
#include <libslic3r/ModelArrange.hpp>
|
||||
|
||||
using namespace std;
|
||||
@ -26,6 +27,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 +51,9 @@ TriangleMesh mesh(TestMesh m)
|
||||
case TestMesh::cube_20x20x20:
|
||||
mesh = Slic3r::make_cube(20, 20, 20);
|
||||
break;
|
||||
case TestMesh::cube_2x20x10:
|
||||
mesh = Slic3r::make_cube(2, 20, 10);
|
||||
break;
|
||||
case TestMesh::sphere_50mm:
|
||||
mesh = Slic3r::make_sphere(50, PI / 243.0);
|
||||
break;
|
||||
@ -224,6 +229,7 @@ void init_print(std::vector<TriangleMesh> &&meshes, Slic3r::Print &print, Slic3r
|
||||
object->add_instance();
|
||||
}
|
||||
arrange_objects(model, InfiniteBed{}, ArrangeParams{ scaled(min_object_distance(config))});
|
||||
model.center_instances_around_point({100, 100});
|
||||
for (ModelObject *mo : model.objects) {
|
||||
mo->ensure_on_bed();
|
||||
print.auto_assign_extruders(mo);
|
||||
@ -348,6 +354,17 @@ std::string slice(std::initializer_list<TriangleMesh> meshes, std::initializer_l
|
||||
return gcode(print);
|
||||
}
|
||||
|
||||
bool contains(const std::string &data, const std::string &pattern)
|
||||
{
|
||||
return data.find(pattern) != data.npos;
|
||||
}
|
||||
|
||||
bool contains_regex(const std::string &data, const std::string &pattern)
|
||||
{
|
||||
boost::regex re(pattern);
|
||||
return boost::regex_match(data, re);
|
||||
}
|
||||
|
||||
} } // namespace Slic3r::Test
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
|
@ -21,6 +21,7 @@ enum class TestMesh {
|
||||
V,
|
||||
_40x10,
|
||||
cube_20x20x20,
|
||||
cube_2x20x10,
|
||||
sphere_50mm,
|
||||
bridge,
|
||||
bridge_with_hole,
|
||||
@ -80,6 +81,9 @@ std::string slice(std::initializer_list<TriangleMesh> meshes, const DynamicPrint
|
||||
std::string slice(std::initializer_list<TestMesh> meshes, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items, bool comments = false);
|
||||
std::string slice(std::initializer_list<TriangleMesh> meshes, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items, bool comments = false);
|
||||
|
||||
bool contains(const std::string &data, const std::string &pattern);
|
||||
bool contains_regex(const std::string &data, const std::string &pattern);
|
||||
|
||||
} } // namespace Slic3r::Test
|
||||
|
||||
|
||||
|
@ -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);
|
||||
Slic3r::GCodeReader parser;
|
||||
const double layer_height = config.opt_float("layer_height");
|
||||
parser.parse_buffer(gcode, [&E_per_mm_bottom, layer_height] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line)
|
||||
{
|
||||
if (self.z() == Approx(layer_height).margin(0.01)) { // only consider first layer
|
||||
if (line.extruding(self) && line.dist_XY(self) > 0) {
|
||||
E_per_mm_bottom.emplace_back(line.dist_E(self) / line.dist_XY(self));
|
||||
}
|
||||
}
|
||||
});
|
||||
THEN(" First layer width applies to everything on first layer.") {
|
||||
bool pass = false;
|
||||
double avg_E = std::accumulate(E_per_mm_bottom.cbegin(), E_per_mm_bottom.cend(), 0.0) / static_cast<double>(E_per_mm_bottom.size());
|
||||
|
||||
pass = (std::count_if(E_per_mm_bottom.cbegin(), E_per_mm_bottom.cend(), [avg_E] (const double& v) { return v == Approx(avg_E); }) == 0);
|
||||
REQUIRE(pass == true);
|
||||
REQUIRE(E_per_mm_bottom.size() > 0); // make sure it actually passed because of extrusion
|
||||
}
|
||||
THEN(" First layer width does not apply to upper layer.") {
|
||||
auto test = [](const DynamicPrintConfig &config) {
|
||||
Slic3r::GCodeReader parser;
|
||||
const double layer_height = config.opt_float("layer_height");
|
||||
std::vector<double> E_per_mm_bottom;
|
||||
parser.parse_buffer(Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config),
|
||||
[&E_per_mm_bottom, layer_height] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line)
|
||||
{
|
||||
if (self.z() == Approx(layer_height).margin(0.01)) { // only consider first layer
|
||||
if (line.extruding(self) && line.dist_XY(self) > 0)
|
||||
E_per_mm_bottom.emplace_back(line.dist_E(self) / line.dist_XY(self));
|
||||
}
|
||||
});
|
||||
THEN("First layer width applies to everything on first layer.") {
|
||||
REQUIRE(E_per_mm_bottom.size() > 0);
|
||||
const double E_per_mm_avg = std::accumulate(E_per_mm_bottom.cbegin(), E_per_mm_bottom.cend(), 0.0) / static_cast<double>(E_per_mm_bottom.size());
|
||||
bool pass = (std::count_if(E_per_mm_bottom.cbegin(), E_per_mm_bottom.cend(), [E_per_mm_avg] (const double& v) { return v == Approx(E_per_mm_avg); }) == 0);
|
||||
REQUIRE(pass);
|
||||
}
|
||||
THEN("First layer width does not apply to upper layer.") {
|
||||
}
|
||||
};
|
||||
GIVEN("A config with a skirt, brim, some fill density, 3 perimeters, and 1 bottom solid layer") {
|
||||
auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
|
||||
{ "skirts", 1 },
|
||||
{ "brim_width", 2 },
|
||||
{ "perimeters", 3 },
|
||||
{ "fill_density", "40%" },
|
||||
{ "first_layer_height", 0.3 },
|
||||
{ "first_layer_extrusion_width", "2" },
|
||||
});
|
||||
WHEN("Slicing a 20mm cube") {
|
||||
test(config);
|
||||
}
|
||||
}
|
||||
GIVEN("A config with more options and a 20mm cube ") {
|
||||
auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
|
||||
{ "skirts", 1 },
|
||||
{ "brim_width", 2 },
|
||||
{ "perimeters", 3 },
|
||||
{ "fill_density", "40%" },
|
||||
{ "layer_height", "0.35" },
|
||||
{ "first_layer_height", "0.35" },
|
||||
{ "bottom_solid_layers", 1 },
|
||||
{ "first_layer_extrusion_width", "2" },
|
||||
{ "filament_diameter", "3" },
|
||||
{ "nozzle_diameter", "0.5" }
|
||||
});
|
||||
WHEN("Slicing a 20mm cube") {
|
||||
test(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
// needs gcode export
|
||||
|
||||
SCENARIO(" Bridge flow specifics.", "[Flow]") {
|
||||
auto config = DynamicPrintConfig::full_print_config_with({
|
||||
{ "bridge_speed", 99 },
|
||||
{ "bridge_flow_ratio", 1 },
|
||||
// to prevent speeds from being altered
|
||||
{ "cooling", "0" },
|
||||
// to prevent speeds from being altered
|
||||
{ "first_layer_speed", "100%" }
|
||||
});
|
||||
|
||||
auto test = [](const DynamicPrintConfig &config) {
|
||||
GCodeReader parser;
|
||||
const double bridge_speed = config.opt_float("bridge_speed") * 60.;
|
||||
std::vector<double> E_per_mm;
|
||||
parser.parse_buffer(Slic3r::Test::slice({ Slic3r::Test::TestMesh::overhang }, config),
|
||||
[&E_per_mm, bridge_speed](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
|
||||
if (line.extruding(self) && line.dist_XY(self) > 0) {
|
||||
if (is_approx<double>(line.new_F(self), bridge_speed))
|
||||
E_per_mm.emplace_back(line.dist_E(self) / line.dist_XY(self));
|
||||
}
|
||||
});
|
||||
const double nozzle_dmr = config.opt<ConfigOptionFloats>("nozzle_diameter")->get_at(0);
|
||||
const double filament_dmr = config.opt<ConfigOptionFloats>("filament_diameter")->get_at(0);
|
||||
const double bridge_mm_per_mm = sqr(nozzle_dmr / filament_dmr) * config.opt_float("bridge_flow_ratio");
|
||||
size_t num_errors = std::count_if(E_per_mm.begin(), E_per_mm.end(),
|
||||
[bridge_mm_per_mm](double v){ return std::abs(v - bridge_mm_per_mm) > 0.01; });
|
||||
return num_errors == 0;
|
||||
};
|
||||
|
||||
GIVEN("A default config with no cooling and a fixed bridge speed, flow ratio and an overhang mesh.") {
|
||||
WHEN("bridge_flow_ratio is set to 1.0") {
|
||||
WHEN("bridge_flow_ratio is set to 0.5 and extrusion width to default") {
|
||||
config.set_deserialize_strict({ { "bridge_flow_ratio", 0.5}, { "extrusion_width", "0" } });
|
||||
THEN("Output flow is as expected.") {
|
||||
REQUIRE(test(config));
|
||||
}
|
||||
}
|
||||
WHEN("bridge_flow_ratio is set to 0.5") {
|
||||
WHEN("bridge_flow_ratio is set to 2.0 and extrusion width to default") {
|
||||
config.set_deserialize_strict({ { "bridge_flow_ratio", 2.0}, { "extrusion_width", "0" } });
|
||||
THEN("Output flow is as expected.") {
|
||||
REQUIRE(test(config));
|
||||
}
|
||||
}
|
||||
WHEN("bridge_flow_ratio is set to 2.0") {
|
||||
WHEN("bridge_flow_ratio is set to 0.5 and extrusion_width to 0.4") {
|
||||
config.set_deserialize_strict({ { "bridge_flow_ratio", 0.5}, { "extrusion_width", 0.4 } });
|
||||
THEN("Output flow is as expected.") {
|
||||
REQUIRE(test(config));
|
||||
}
|
||||
}
|
||||
WHEN("bridge_flow_ratio is set to 1.0 and extrusion_width to 0.4") {
|
||||
config.set_deserialize_strict({ { "bridge_flow_ratio", 1.0}, { "extrusion_width", 0.4 } });
|
||||
THEN("Output flow is as expected.") {
|
||||
REQUIRE(test(config));
|
||||
}
|
||||
}
|
||||
WHEN("bridge_flow_ratio is set to 2 and extrusion_width to 0.4") {
|
||||
config.set_deserialize_strict({ { "bridge_flow_ratio", 2.}, { "extrusion_width", 0.4 } });
|
||||
THEN("Output flow is as expected.") {
|
||||
REQUIRE(test(config));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -126,3 +126,62 @@ SCENARIO("Print: Brim generation", "[Print]") {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("Ported from Perl", "[Print]") {
|
||||
GIVEN("20mm cube") {
|
||||
WHEN("Print center is set to 100x100 (test framework default)") {
|
||||
auto config = Slic3r::DynamicPrintConfig::full_print_config();
|
||||
std::string gcode = Slic3r::Test::slice({ TestMesh::cube_20x20x20 }, config);
|
||||
GCodeReader parser;
|
||||
Points extrusion_points;
|
||||
parser.parse_buffer(gcode, [&extrusion_points](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line)
|
||||
{
|
||||
if (line.cmd_is("G1") && line.extruding(self) && line.dist_XY(self) > 0)
|
||||
extrusion_points.emplace_back(line.new_XY_scaled(self));
|
||||
});
|
||||
Vec2d center = unscaled<double>(BoundingBox(extrusion_points).center());
|
||||
THEN("print is centered around print_center") {
|
||||
REQUIRE(is_approx(center.x(), 100.));
|
||||
REQUIRE(is_approx(center.y(), 100.));
|
||||
}
|
||||
}
|
||||
}
|
||||
GIVEN("Model with multiple objects") {
|
||||
auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
|
||||
{ "nozzle_diameter", { 0.4, 0.4, 0.4, 0.4 } }
|
||||
});
|
||||
Print print;
|
||||
Model model;
|
||||
Slic3r::Test::init_print({ TestMesh::cube_20x20x20 }, print, model, config);
|
||||
|
||||
// User sets a per-region option, also testing a deep copy of Model.
|
||||
Model model2(model);
|
||||
model2.objects.front()->config.set_deserialize_strict("fill_density", "100%");
|
||||
WHEN("fill_density overridden") {
|
||||
print.apply(model2, config);
|
||||
THEN("region config inherits model object config") {
|
||||
REQUIRE(print.get_print_region(0).config().fill_density == 100);
|
||||
}
|
||||
}
|
||||
|
||||
model2.objects.front()->config.erase("fill_density");
|
||||
WHEN("fill_density resetted") {
|
||||
print.apply(model2, config);
|
||||
THEN("region config is resetted") {
|
||||
REQUIRE(print.get_print_region(0).config().fill_density == 20);
|
||||
}
|
||||
}
|
||||
|
||||
WHEN("extruder is assigned") {
|
||||
model2.objects.front()->config.set("extruder", 3);
|
||||
model2.objects.front()->config.set("perimeter_extruder", 2);
|
||||
print.apply(model2, config);
|
||||
THEN("extruder setting is correctly expanded") {
|
||||
REQUIRE(print.get_print_region(0).config().infill_extruder == 3);
|
||||
}
|
||||
THEN("extruder setting does not override explicitely specified extruders") {
|
||||
REQUIRE(print.get_print_region(0).config().perimeter_extruder == 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ TEST_CASE("Skirt height is honored", "[Skirt]") {
|
||||
{ "support_material_speed", 99 },
|
||||
// avoid altering speeds unexpectedly
|
||||
{ "cooling", false },
|
||||
// avoid altering speeds unexpectedly
|
||||
{ "first_layer_speed", "100%" }
|
||||
});
|
||||
|
||||
|
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
${_TEST_NAME}_tests.cpp
|
||||
test_3mf.cpp
|
||||
test_aabbindirect.cpp
|
||||
test_kdtreeindirect.cpp
|
||||
test_clipper_offset.cpp
|
||||
test_clipper_utils.cpp
|
||||
test_color.cpp
|
||||
@ -13,6 +14,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_geometry.cpp
|
||||
test_placeholder_parser.cpp
|
||||
test_polygon.cpp
|
||||
test_polyline.cpp
|
||||
test_mutable_polygon.cpp
|
||||
test_mutable_priority_queue.cpp
|
||||
test_stl.cpp
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/LocalesUtils.hpp"
|
||||
|
||||
@ -13,20 +14,20 @@ using namespace Slic3r;
|
||||
SCENARIO("Generic config validation performs as expected.", "[Config]") {
|
||||
GIVEN("A config generated from default options") {
|
||||
Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
|
||||
WHEN( "perimeter_extrusion_width is set to 250%, a valid value") {
|
||||
WHEN("perimeter_extrusion_width is set to 250%, a valid value") {
|
||||
config.set_deserialize_strict("perimeter_extrusion_width", "250%");
|
||||
THEN( "The config is read as valid.") {
|
||||
REQUIRE(config.validate().empty());
|
||||
}
|
||||
}
|
||||
WHEN( "perimeter_extrusion_width is set to -10, an invalid value") {
|
||||
WHEN("perimeter_extrusion_width is set to -10, an invalid value") {
|
||||
config.set("perimeter_extrusion_width", -10);
|
||||
THEN( "Validate returns error") {
|
||||
REQUIRE(! config.validate().empty());
|
||||
}
|
||||
}
|
||||
|
||||
WHEN( "perimeters is set to -10, an invalid value") {
|
||||
WHEN("perimeters is set to -10, an invalid value") {
|
||||
config.set("perimeters", -10);
|
||||
THEN( "Validate returns error") {
|
||||
REQUIRE(! config.validate().empty());
|
||||
@ -36,8 +37,7 @@ SCENARIO("Generic config validation performs as expected.", "[Config]") {
|
||||
}
|
||||
|
||||
SCENARIO("Config accessor functions perform as expected.", "[Config]") {
|
||||
GIVEN("A config generated from default options") {
|
||||
Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
|
||||
auto test = [](ConfigBase &config) {
|
||||
WHEN("A boolean option is set to a boolean value") {
|
||||
REQUIRE_NOTHROW(config.set("gcode_comments", true));
|
||||
THEN("The underlying value is set correctly.") {
|
||||
@ -70,8 +70,8 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") {
|
||||
}
|
||||
}
|
||||
#if 0
|
||||
//FIXME better design accessors for vector elements.
|
||||
WHEN("An integer-based option is set through the integer interface") {
|
||||
//FIXME better design accessors for vector elements.
|
||||
WHEN("An integer-based option is set through the integer interface") {
|
||||
config.set("bed_temperature", 100);
|
||||
THEN("The underlying value is set correctly.") {
|
||||
REQUIRE(config.opt<ConfigOptionInts>("bed_temperature")->get_at(0) == 100);
|
||||
@ -193,6 +193,14 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") {
|
||||
REQUIRE(config.opt_float("layer_height") == 0.5);
|
||||
}
|
||||
}
|
||||
};
|
||||
GIVEN("DynamicPrintConfig generated from default options") {
|
||||
auto config = Slic3r::DynamicPrintConfig::full_print_config();
|
||||
test(config);
|
||||
}
|
||||
GIVEN("FullPrintConfig generated from default options") {
|
||||
Slic3r::FullPrintConfig config;
|
||||
test(config);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,14 +162,34 @@ TEST_CASE("Splitting a Polygon generates a polyline correctly", "[Geometry]"){
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("Bounding boxes are scaled appropriately", "[Geometry]"){
|
||||
BoundingBox bb(std::vector<Point>({Point(0, 1), Point(10, 2), Point(20, 2)}));
|
||||
bb.scale(2);
|
||||
REQUIRE(bb.min == Point(0,2));
|
||||
REQUIRE(bb.max == Point(40,4));
|
||||
SCENARIO("BoundingBox", "[Geometry]") {
|
||||
WHEN("Bounding boxes are scaled") {
|
||||
BoundingBox bb(std::vector<Point>({Point(0, 1), Point(10, 2), Point(20, 2)}));
|
||||
bb.scale(2);
|
||||
REQUIRE(bb.min == Point(0,2));
|
||||
REQUIRE(bb.max == Point(40,4));
|
||||
}
|
||||
WHEN("BoundingBox constructed from points") {
|
||||
BoundingBox bb(Points{ {100,200}, {100, 200}, {500, -600} });
|
||||
THEN("minimum is correct") {
|
||||
REQUIRE(bb.min == Point{100,-600});
|
||||
}
|
||||
THEN("maximum is correct") {
|
||||
REQUIRE(bb.max == Point{500,200});
|
||||
}
|
||||
}
|
||||
WHEN("BoundingBox constructed from a single point") {
|
||||
BoundingBox bb;
|
||||
bb.merge({10, 10});
|
||||
THEN("minimum equals to the only defined point") {
|
||||
REQUIRE(bb.min == Point{10,10});
|
||||
}
|
||||
THEN("maximum equals to the only defined point") {
|
||||
REQUIRE(bb.max == Point{10,10});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("Offseting a line generates a polygon correctly", "[Geometry]"){
|
||||
Slic3r::Polyline tmp = { Point(10,10), Point(20,10) };
|
||||
Slic3r::Polygon area = offset(tmp,5).at(0);
|
||||
|
142
tests/libslic3r/test_kdtreeindirect.cpp
Normal file
142
tests/libslic3r/test_kdtreeindirect.cpp
Normal file
@ -0,0 +1,142 @@
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "libslic3r/KDTreeIndirect.hpp"
|
||||
#include "libslic3r/Execution/ExecutionSeq.hpp"
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "libslic3r/PointGrid.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
//template<class G>
|
||||
//struct Within { // Wrapper for the `within` predicate that counts calls.
|
||||
|
||||
// kdtree::Within<G> pred;
|
||||
|
||||
// Within(G box): pred{box} {}
|
||||
|
||||
// // Number of times the predicate was called
|
||||
// mutable size_t call_count = 0;
|
||||
|
||||
// std::pair<bool, unsigned int> operator() (const Vec3f &p, size_t dim)
|
||||
// {
|
||||
// ++call_count;
|
||||
|
||||
// return pred(p, dim);
|
||||
// }
|
||||
//};
|
||||
|
||||
static double volume(const BoundingBox3Base<Vec3f> &box)
|
||||
{
|
||||
auto sz = box.size();
|
||||
return sz.x() * sz.y() * sz.z();
|
||||
}
|
||||
|
||||
static double volume(const Eigen::AlignedBox<float, 3> &box)
|
||||
{
|
||||
return box.volume();
|
||||
}
|
||||
|
||||
TEST_CASE("Test kdtree query for a Box", "[KDTreeIndirect]")
|
||||
{
|
||||
auto vol = BoundingBox3Base<Vec3f>{{0.f, 0.f, 0.f}, {10.f, 10.f, 10.f}};
|
||||
|
||||
auto pgrid = point_grid(ex_seq, vol, Vec3f{0.1f, 0.1f, 0.1f});
|
||||
|
||||
REQUIRE(!pgrid.empty());
|
||||
|
||||
auto coordfn = [&pgrid] (size_t i, size_t D) { return pgrid.get(i)(int(D)); };
|
||||
KDTreeIndirect<3, float, decltype(coordfn)> tree{coordfn, pgrid.point_count()};
|
||||
|
||||
std::vector<size_t> out;
|
||||
|
||||
auto qbox = BoundingBox3Base{Vec3f{0.f, 0.f, 0.f}, Vec3f{.5f, .5f, .5f}};
|
||||
|
||||
size_t call_count = 0;
|
||||
out = find_nearby_points(tree, qbox.min, qbox.max, [&call_count](size_t) {
|
||||
call_count++;
|
||||
return true;
|
||||
});
|
||||
|
||||
// Output shall be non-empty
|
||||
REQUIRE(!out.empty());
|
||||
|
||||
std::sort(out.begin(), out.end());
|
||||
|
||||
// No duplicates allowed in the output
|
||||
auto it = std::unique(out.begin(), out.end());
|
||||
REQUIRE(it == out.end());
|
||||
|
||||
// Test if inside points are in the output and outside points are not.
|
||||
bool succ = true;
|
||||
for (size_t i = 0; i < pgrid.point_count(); ++i) {
|
||||
auto foundit = std::find(out.begin(), out.end(), i);
|
||||
bool contains = qbox.contains(pgrid.get(i));
|
||||
succ = succ && contains ? foundit != out.end() : foundit == out.end();
|
||||
|
||||
if (!succ) {
|
||||
std::cout << "invalid point: " << i << " " << pgrid.get(i).transpose()
|
||||
<< std::endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
REQUIRE(succ);
|
||||
|
||||
// Test for the expected cost of the query.
|
||||
double gridvolume = volume(vol);
|
||||
double queryvolume = volume(qbox);
|
||||
double volratio = (queryvolume / gridvolume);
|
||||
REQUIRE(call_count < 3 * volratio * pgrid.point_count());
|
||||
REQUIRE(call_count < pgrid.point_count());
|
||||
}
|
||||
|
||||
//TEST_CASE("Test kdtree query for a Sphere", "[KDTreeIndirect]") {
|
||||
// auto vol = BoundingBox3Base<Vec3f>{{0.f, 0.f, 0.f}, {10.f, 10.f, 10.f}};
|
||||
|
||||
// auto pgrid = point_grid(ex_seq, vol, Vec3f{0.1f, 0.1f, 0.1f});
|
||||
|
||||
// REQUIRE(!pgrid.empty());
|
||||
|
||||
// auto coordfn = [&pgrid] (size_t i, size_t D) { return pgrid.get(i)(int(D)); };
|
||||
// kdtree::KDTreeIndirect<3, float, decltype(coordfn)> tree{coordfn, pgrid.point_count()};
|
||||
|
||||
// std::vector<size_t> out;
|
||||
|
||||
// auto querysphere = kdtree::Sphere{Vec3f{5.f, 5.f, 5.f}, 2.f};
|
||||
|
||||
// auto pred = Within(querysphere);
|
||||
|
||||
// kdtree::query(tree, pred, std::back_inserter(out));
|
||||
|
||||
// // Output shall be non-empty
|
||||
// REQUIRE(!out.empty());
|
||||
|
||||
// std::sort(out.begin(), out.end());
|
||||
|
||||
// // No duplicates allowed in the output
|
||||
// auto it = std::unique(out.begin(), out.end());
|
||||
// REQUIRE(it == out.end());
|
||||
|
||||
// // Test if inside points are in the output and outside points are not.
|
||||
// bool succ = true;
|
||||
// for (size_t i = 0; i < pgrid.point_count(); ++i) {
|
||||
// auto foundit = std::find(out.begin(), out.end(), i);
|
||||
// bool contains = (querysphere.center - pgrid.get(i)).squaredNorm() < pred.pred.r2;
|
||||
// succ = succ && contains ? foundit != out.end() : foundit == out.end();
|
||||
|
||||
// if (!succ) {
|
||||
// std::cout << "invalid point: " << i << " " << pgrid.get(i).transpose()
|
||||
// << std::endl;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
// REQUIRE(succ);
|
||||
|
||||
// // Test for the expected cost of the query.
|
||||
// double gridvolume = volume(vol);
|
||||
// double queryvolume = volume(querysphere);
|
||||
// double volratio = (queryvolume / gridvolume);
|
||||
// REQUIRE(pred.call_count < 3 * volratio * pgrid.point_count());
|
||||
// REQUIRE(pred.call_count < pgrid.point_count());
|
||||
//}
|
@ -343,7 +343,7 @@ TEST_CASE("Mutable priority queue - reshedule first", "[MutableSkipHeapPriorityQ
|
||||
TEST_CASE("Mutable priority queue - first pop", "[MutableSkipHeapPriorityQueue]")
|
||||
{
|
||||
struct MyValue{
|
||||
int id;
|
||||
size_t id;
|
||||
float val;
|
||||
};
|
||||
size_t count = 50000;
|
||||
@ -356,15 +356,15 @@ TEST_CASE("Mutable priority queue - first pop", "[MutableSkipHeapPriorityQueue]"
|
||||
[](MyValue &l, MyValue &r) { return l.val < r.val; });
|
||||
q.reserve(count);
|
||||
for (size_t id = 0; id < count; id++) {
|
||||
MyValue mv;
|
||||
mv.id = id;
|
||||
mv.val = rand();
|
||||
MyValue mv{ id, rand() / 100.f };
|
||||
q.push(mv);
|
||||
}
|
||||
MyValue it = q.top(); // copy
|
||||
q.pop();
|
||||
bool valid = (it.id != 0) && (idxs[0] < 3 * count);
|
||||
CHECK(valid);
|
||||
// is valid id (no initial value default value)
|
||||
CHECK(it.id != 0);
|
||||
// is first item in queue valid value
|
||||
CHECK(idxs[0] != std::numeric_limits<size_t>::max());
|
||||
}
|
||||
|
||||
TEST_CASE("Mutable priority queue complex", "[MutableSkipHeapPriorityQueue]")
|
||||
|
@ -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} });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -386,7 +386,7 @@ long raster_pxsum(const sla::RasterGrayscaleAA &raster)
|
||||
|
||||
double raster_white_area(const sla::RasterGrayscaleAA &raster)
|
||||
{
|
||||
if (raster.resolution().pixels() == 0) return std::nan("");
|
||||
if (raster.resolution().pixels() == 0) return NaNd;
|
||||
|
||||
auto res = raster.resolution();
|
||||
double a = 0;
|
||||
|
@ -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()))); %};
|
||||
};
|
@ -60,8 +60,6 @@
|
||||
size_t object_count()
|
||||
%code%{ RETVAL = THIS->objects().size(); %};
|
||||
|
||||
PrintRegionPtrs* regions()
|
||||
%code%{ RETVAL = const_cast<PrintRegionPtrs*>(&THIS->print_regions_mutable()); %};
|
||||
|
||||
void auto_assign_extruders(ModelObject* model_object);
|
||||
std::string output_filepath(std::string path = "")
|
||||
|
@ -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…
x
Reference in New Issue
Block a user