Merge remote-tracking branch 'origin/master' into ys_color_print_extension

This commit is contained in:
YuSanka 2019-10-18 13:05:24 +02:00
commit 7ef9af8aed
36 changed files with 2266 additions and 504 deletions

View File

@ -173,10 +173,10 @@ if (NOT MSVC AND ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMP
# On GCC and Clang, no return from a non-void function is a warning only. Here, we make it an error.
add_compile_options(-Werror=return-type)
#removes LOTS of extraneous Eigen warnings (GCC only supports it since 6.1)
#https://eigen.tuxfamily.org/bz/show_bug.cgi?id=1221
if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" OR CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 6.1)
add_compile_options(-Wno-ignored-attributes)
# removes LOTS of extraneous Eigen warnings (GCC only supports it since 6.1)
# https://eigen.tuxfamily.org/bz/show_bug.cgi?id=1221
if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" OR CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 6.0)
add_compile_options(-Wno-ignored-attributes) # Tamas: Eigen include dirs are marked as SYSTEM
endif()
#GCC generates loads of -Wunknown-pragmas when compiling igl. The fix is not easy due to a bug in gcc, see

View File

@ -260,6 +260,10 @@ if(NOT TBB_FOUND)
if(TBB_LIBRARIES_${TBB_BUILD_TYPE})
set(TBB_LIBRARIES "${TBB_LIBRARIES_${TBB_BUILD_TYPE}}")
endif()
if(NOT MSVC AND NOT TBB_LIBRARIES)
set(TBB_LIBRARIES ${TBB_LIBRARIES_RELEASE})
endif()
if (MSVC AND TBB_STATIC)
set(TBB_DEFINITIONS __TBB_NO_IMPLICIT_LINKAGE)

View File

@ -425,7 +425,30 @@ std::string ConfigBase::opt_serialize(const t_config_option_key &opt_key) const
return opt->serialize();
}
bool ConfigBase::set_deserialize(const t_config_option_key &opt_key_src, const std::string &value_src, bool append)
void ConfigBase::set(const std::string &opt_key, int value, bool create)
{
ConfigOption *opt = this->option_throw(opt_key, create);
switch (opt->type()) {
case coInt: static_cast<ConfigOptionInt*>(opt)->value = value; break;
case coFloat: static_cast<ConfigOptionFloat*>(opt)->value = value; break;
case coFloatOrPercent: static_cast<ConfigOptionFloatOrPercent*>(opt)->value = value; static_cast<ConfigOptionFloatOrPercent*>(opt)->percent = false; break;
case coString: static_cast<ConfigOptionString*>(opt)->value = std::to_string(value); break;
default: throw BadOptionTypeException("Configbase::set() - conversion from int not possible");
}
}
void ConfigBase::set(const std::string &opt_key, double value, bool create)
{
ConfigOption *opt = this->option_throw(opt_key, create);
switch (opt->type()) {
case coFloat: static_cast<ConfigOptionFloat*>(opt)->value = value; break;
case coFloatOrPercent: static_cast<ConfigOptionFloatOrPercent*>(opt)->value = value; static_cast<ConfigOptionFloatOrPercent*>(opt)->percent = false; break;
case coString: static_cast<ConfigOptionString*>(opt)->value = std::to_string(value); break;
default: throw BadOptionTypeException("Configbase::set() - conversion from float not possible");
}
}
bool ConfigBase::set_deserialize_nothrow(const t_config_option_key &opt_key_src, const std::string &value_src, bool append)
{
t_config_option_key opt_key = opt_key_src;
std::string value = value_src;
@ -438,6 +461,18 @@ bool ConfigBase::set_deserialize(const t_config_option_key &opt_key_src, const s
return this->set_deserialize_raw(opt_key, value, append);
}
void ConfigBase::set_deserialize(const t_config_option_key &opt_key_src, const std::string &value_src, bool append)
{
if (! this->set_deserialize_nothrow(opt_key_src, value_src, append))
throw BadOptionTypeException("ConfigBase::set_deserialize() failed");
}
void ConfigBase::set_deserialize(std::initializer_list<SetDeserializeItem> items)
{
for (const SetDeserializeItem &item : items)
this->set_deserialize(item.opt_key, item.opt_value, item.append);
}
bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, const std::string &value, bool append)
{
t_config_option_key opt_key = opt_key_src;
@ -668,6 +703,12 @@ void ConfigBase::null_nullables()
}
}
DynamicConfig::DynamicConfig(const ConfigBase& rhs, const t_config_option_keys& keys)
{
for (const t_config_option_key& opt_key : keys)
this->options[opt_key] = std::unique_ptr<ConfigOption>(rhs.option(opt_key)->clone());
}
bool DynamicConfig::operator==(const DynamicConfig &rhs) const
{
auto it1 = this->options.begin();
@ -817,7 +858,7 @@ bool DynamicConfig::read_cli(int argc, char** argv, t_config_option_keys* extra,
static_cast<ConfigOptionString*>(opt_base)->value = value;
} else {
// Any scalar value of a type different from Bool and String.
if (! this->set_deserialize(opt_key, value, false)) {
if (! this->set_deserialize_nothrow(opt_key, value, false)) {
boost::nowide::cerr << "Invalid value supplied for --" << token.c_str() << std::endl;
return false;
}

View File

@ -52,6 +52,16 @@ public:
std::runtime_error(std::string("No definition exception: ") + opt_key) {}
};
/// Indicate that an unsupported accessor was called on a config option.
class BadOptionTypeException : public std::runtime_error
{
public:
BadOptionTypeException() :
std::runtime_error("Bad option type exception") {}
BadOptionTypeException(const char* message) :
std::runtime_error(message) {}
};
// Type of a configuration value.
enum ConfigOptionType {
coVectorType = 0x4000,
@ -117,10 +127,10 @@ public:
virtual ConfigOption* clone() const = 0;
// Set a value from a ConfigOption. The two options should be compatible.
virtual void set(const ConfigOption *option) = 0;
virtual int getInt() const { throw std::runtime_error("Calling ConfigOption::getInt on a non-int ConfigOption"); }
virtual double getFloat() const { throw std::runtime_error("Calling ConfigOption::getFloat on a non-float ConfigOption"); }
virtual bool getBool() const { throw std::runtime_error("Calling ConfigOption::getBool on a non-boolean ConfigOption"); }
virtual void setInt(int /* val */) { throw std::runtime_error("Calling ConfigOption::setInt on a non-int ConfigOption"); }
virtual int getInt() const { throw BadOptionTypeException("Calling ConfigOption::getInt on a non-int ConfigOption"); }
virtual double getFloat() const { throw BadOptionTypeException("Calling ConfigOption::getFloat on a non-float ConfigOption"); }
virtual bool getBool() const { throw BadOptionTypeException("Calling ConfigOption::getBool on a non-boolean ConfigOption"); }
virtual void setInt(int /* val */) { throw BadOptionTypeException("Calling ConfigOption::setInt on a non-int ConfigOption"); }
virtual bool operator==(const ConfigOption &rhs) const = 0;
bool operator!=(const ConfigOption &rhs) const { return ! (*this == rhs); }
bool is_scalar() const { return (int(this->type()) & int(coVectorType)) == 0; }
@ -1513,32 +1523,48 @@ protected:
public:
// Non-virtual methods:
bool has(const t_config_option_key &opt_key) const { return this->option(opt_key) != nullptr; }
const ConfigOption* option(const t_config_option_key &opt_key) const
{ return const_cast<ConfigBase*>(this)->option(opt_key, false); }
ConfigOption* option(const t_config_option_key &opt_key, bool create = false)
{ return this->optptr(opt_key, create); }
template<typename TYPE>
TYPE* option(const t_config_option_key &opt_key, bool create = false)
{
ConfigOption *opt = this->optptr(opt_key, create);
return (opt == nullptr || opt->type() != TYPE::static_type()) ? nullptr : static_cast<TYPE*>(opt);
}
template<typename TYPE>
const TYPE* option(const t_config_option_key &opt_key) const
{ return const_cast<ConfigBase*>(this)->option<TYPE>(opt_key, false); }
template<typename TYPE>
TYPE* option_throw(const t_config_option_key &opt_key, bool create = false)
ConfigOption* option_throw(const t_config_option_key &opt_key, bool create = false)
{
ConfigOption *opt = this->optptr(opt_key, create);
if (opt == nullptr)
throw UnknownOptionException(opt_key);
return opt;
}
const ConfigOption* option_throw(const t_config_option_key &opt_key) const
{ return const_cast<ConfigBase*>(this)->option_throw(opt_key, false); }
template<typename TYPE>
TYPE* option_throw(const t_config_option_key &opt_key, bool create = false)
{
ConfigOption *opt = this->option_throw(opt_key, create);
if (opt->type() != TYPE::static_type())
throw std::runtime_error("Conversion to a wrong type");
throw BadOptionTypeException("Conversion to a wrong type");
return static_cast<TYPE*>(opt);
}
template<typename TYPE>
const TYPE* option_throw(const t_config_option_key &opt_key) const
{ return const_cast<ConfigBase*>(this)->option_throw<TYPE>(opt_key, false); }
// Apply all keys of other ConfigBase defined by this->def() to this ConfigBase.
// An UnknownOptionException is thrown in case some option keys of other are not defined by this->def(),
// or this ConfigBase is of a StaticConfig type and it does not support some of the keys, and ignore_nonexistent is not set.
@ -1551,9 +1577,40 @@ public:
t_config_option_keys diff(const ConfigBase &other) const;
t_config_option_keys equal(const ConfigBase &other) const;
std::string opt_serialize(const t_config_option_key &opt_key) const;
// Set a value. Convert numeric types using a C style implicit conversion / promotion model.
// Throw if option is not avaiable and create is not enabled,
// or if the conversion is not possible.
// Conversion to string is always possible.
void set(const std::string &opt_key, bool value, bool create = false)
{ this->option_throw<ConfigOptionBool>(opt_key, create)->value = value; }
void set(const std::string &opt_key, int value, bool create = false);
void set(const std::string &opt_key, double value, bool create = false);
void set(const std::string &opt_key, const char *value, bool create = false)
{ this->option_throw<ConfigOptionString>(opt_key, create)->value = value; }
void set(const std::string &opt_key, const std::string &value, bool create = false)
{ this->option_throw<ConfigOptionString>(opt_key, create)->value = value; }
// Set a configuration value from a string, it will call an overridable handle_legacy()
// to resolve renamed and removed configuration keys.
bool set_deserialize(const t_config_option_key &opt_key, const std::string &str, bool append = false);
bool set_deserialize_nothrow(const t_config_option_key &opt_key_src, const std::string &value_src, bool append = false);
// May throw BadOptionTypeException() if the operation fails.
void set_deserialize(const t_config_option_key &opt_key, const std::string &str, bool append = false);
struct SetDeserializeItem {
SetDeserializeItem(const char *opt_key, const char *opt_value, bool append = false) : opt_key(opt_key), opt_value(opt_value), append(append) {}
SetDeserializeItem(const std::string &opt_key, const std::string &opt_value, bool append = false) : opt_key(opt_key), opt_value(opt_value), append(append) {}
SetDeserializeItem(const char *opt_key, const bool value, bool append = false) : opt_key(opt_key), opt_value(value ? "1" : "0"), append(append) {}
SetDeserializeItem(const std::string &opt_key, const bool value, bool append = false) : opt_key(opt_key), opt_value(value ? "1" : "0"), append(append) {}
SetDeserializeItem(const char *opt_key, const int value, bool append = false) : opt_key(opt_key), opt_value(std::to_string(value)), append(append) {}
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 float value, bool append = false) : opt_key(opt_key), opt_value(std::to_string(value)), append(append) {}
SetDeserializeItem(const std::string &opt_key, const float value, bool append = false) : opt_key(opt_key), opt_value(std::to_string(value)), append(append) {}
SetDeserializeItem(const char *opt_key, const double value, bool append = false) : opt_key(opt_key), opt_value(std::to_string(value)), append(append) {}
SetDeserializeItem(const std::string &opt_key, const double value, bool append = false) : opt_key(opt_key), opt_value(std::to_string(value)), append(append) {}
std::string opt_key; std::string opt_value; bool append = false;
};
// May throw BadOptionTypeException() if the operation fails.
void set_deserialize(std::initializer_list<SetDeserializeItem> items);
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;
@ -1580,9 +1637,11 @@ class DynamicConfig : public virtual ConfigBase
{
public:
DynamicConfig() {}
DynamicConfig(const DynamicConfig& other) { *this = other; }
DynamicConfig(DynamicConfig&& other) : options(std::move(other.options)) { other.options.clear(); }
virtual ~DynamicConfig() override { clear(); }
DynamicConfig(const DynamicConfig &rhs) { *this = rhs; }
DynamicConfig(DynamicConfig &&rhs) : options(std::move(rhs.options)) { rhs.options.clear(); }
explicit DynamicConfig(const ConfigBase &rhs, const t_config_option_keys &keys);
explicit DynamicConfig(const ConfigBase& rhs) : DynamicConfig(rhs, rhs.keys()) {}
virtual ~DynamicConfig() override { clear(); }
// Copy a content of one DynamicConfig to another DynamicConfig.
// If rhs.def() is not null, then it has to be equal to this->def().

View File

@ -18,15 +18,20 @@ class ExPolygon
{
public:
ExPolygon() {}
ExPolygon(const ExPolygon &other) : contour(other.contour), holes(other.holes) {}
ExPolygon(const ExPolygon &other) : contour(other.contour), holes(other.holes) {}
ExPolygon(ExPolygon &&other) : contour(std::move(other.contour)), holes(std::move(other.holes)) {}
explicit ExPolygon(const Polygon &contour) : contour(contour) {}
explicit ExPolygon(Polygon &&contour) : contour(std::move(contour)) {}
explicit ExPolygon(const Points &contour) : contour(contour) {}
explicit ExPolygon(Points &&contour) : contour(std::move(contour)) {}
explicit ExPolygon(const Polygon &contour, const Polygon &hole) : contour(contour) { holes.emplace_back(hole); }
explicit ExPolygon(Polygon &&contour, Polygon &&hole) : contour(std::move(contour)) { holes.emplace_back(std::move(hole)); }
explicit ExPolygon(const Points &contour, const Points &hole) : contour(contour) { holes.emplace_back(hole); }
explicit ExPolygon(Points &&contour, Polygon &&hole) : contour(std::move(contour)) { holes.emplace_back(std::move(hole)); }
ExPolygon& operator=(const ExPolygon &other) { contour = other.contour; holes = other.holes; return *this; }
ExPolygon& operator=(ExPolygon &&other) { contour = std::move(other.contour); holes = std::move(other.holes); return *this; }
inline explicit ExPolygon(const Polygon &p): contour(p) {}
inline explicit ExPolygon(Polygon &&p): contour(std::move(p)) {}
Polygon contour;
Polygons holes;

View File

@ -126,18 +126,26 @@ size_t ExtrusionEntityCollection::items_count() const
}
// Returns a single vector of pointers to all non-collection items contained in this one.
ExtrusionEntityCollection ExtrusionEntityCollection::flatten() const
ExtrusionEntityCollection ExtrusionEntityCollection::flatten(bool preserve_ordering) const
{
struct Flatten {
Flatten(bool preserve_ordering) : preserve_ordering(preserve_ordering) {}
ExtrusionEntityCollection out;
bool preserve_ordering;
void recursive_do(const ExtrusionEntityCollection &collection) {
for (const ExtrusionEntity* entity : collection.entities)
if (entity->is_collection())
this->recursive_do(*static_cast<const ExtrusionEntityCollection*>(entity));
else
out.append(*entity);
if (collection.no_sort && preserve_ordering) {
// Don't flatten whatever happens below this level.
out.append(collection);
} else {
for (const ExtrusionEntity *entity : collection.entities)
if (entity->is_collection())
this->recursive_do(*static_cast<const ExtrusionEntityCollection*>(entity));
else
out.append(*entity);
}
}
} flatten;
} flatten(preserve_ordering);
flatten.recursive_do(*this);
return flatten.out;
}

View File

@ -15,17 +15,17 @@ public:
ExtrusionEntitiesPtr entities; // we own these entities
bool no_sort;
ExtrusionEntityCollection(): no_sort(false) {};
ExtrusionEntityCollection(): no_sort(false) {}
ExtrusionEntityCollection(const ExtrusionEntityCollection &other) : no_sort(other.no_sort) { this->append(other.entities); }
ExtrusionEntityCollection(ExtrusionEntityCollection &&other) : entities(std::move(other.entities)), no_sort(other.no_sort) {}
explicit ExtrusionEntityCollection(const ExtrusionPaths &paths);
ExtrusionEntityCollection& operator=(const ExtrusionEntityCollection &other);
ExtrusionEntityCollection& operator=(ExtrusionEntityCollection &&other)
ExtrusionEntityCollection& operator=(ExtrusionEntityCollection &&other)
{ this->entities = std::move(other.entities); this->no_sort = other.no_sort; return *this; }
~ExtrusionEntityCollection() { clear(); }
explicit operator ExtrusionPaths() const;
bool is_collection() const { return true; };
bool is_collection() const { return true; }
ExtrusionRole role() const override {
ExtrusionRole out = erNone;
for (const ExtrusionEntity *ee : entities) {
@ -34,8 +34,8 @@ public:
}
return out;
}
bool can_reverse() const { return !this->no_sort; };
bool empty() const { return this->entities.empty(); };
bool can_reverse() const { return !this->no_sort; }
bool empty() const { return this->entities.empty(); }
void clear();
void swap (ExtrusionEntityCollection &c);
void append(const ExtrusionEntity &entity) { this->entities.emplace_back(entity.clone()); }
@ -81,7 +81,10 @@ public:
Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const
{ Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; }
size_t items_count() const;
ExtrusionEntityCollection flatten() const;
/// Returns a flattened copy of this ExtrusionEntityCollection. That is, all of the items in its entities vector are not collections.
/// You should be iterating over flatten().entities if you are interested in the underlying ExtrusionEntities (and don't care about hierarchy).
/// \param preserve_ordering Flag to method that will flatten if and only if the underlying collection is sortable when True (default: False).
ExtrusionEntityCollection flatten(bool preserve_ordering = false) const;
double min_mm3_per_mm() const;
double total_volume() const override { double volume=0.; for (const auto& ent : entities) volume+=ent->total_volume(); return volume; }

View File

@ -29,8 +29,6 @@ public:
FillParams params;
};
void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out);
} // namespace Slic3r
#endif // slic3r_Fill_hpp_

View File

@ -15,6 +15,7 @@
namespace Slic3r {
class ExPolygon;
class Surface;
struct FillParams

View File

@ -141,6 +141,7 @@ void GCodeAnalyzer::reset()
_set_start_extrusion(DEFAULT_START_EXTRUSION);
_set_fan_speed(DEFAULT_FAN_SPEED);
_reset_axes_position();
_reset_axes_origin();
_reset_cached_position();
m_moves_map.clear();
@ -310,31 +311,32 @@ void GCodeAnalyzer::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLi
m_process_output += line.raw() + "\n";
}
// Returns the new absolute position on the given axis in dependence of the given parameters
float axis_absolute_position_from_G1_line(GCodeAnalyzer::EAxis axis, const GCodeReader::GCodeLine& lineG1, GCodeAnalyzer::EUnits units, bool is_relative, float current_absolute_position)
{
float lengthsScaleFactor = (units == GCodeAnalyzer::Inches) ? INCHES_TO_MM : 1.0f;
if (lineG1.has(Slic3r::Axis(axis)))
{
float ret = lineG1.value(Slic3r::Axis(axis)) * lengthsScaleFactor;
return is_relative ? current_absolute_position + ret : ret;
}
else
return current_absolute_position;
}
void GCodeAnalyzer::_processG1(const GCodeReader::GCodeLine& line)
{
auto axis_absolute_position = [this](GCodeAnalyzer::EAxis axis, const GCodeReader::GCodeLine& lineG1) -> float
{
float current_absolute_position = _get_axis_position(axis);
float current_origin = _get_axis_origin(axis);
float lengthsScaleFactor = (_get_units() == GCodeAnalyzer::Inches) ? INCHES_TO_MM : 1.0f;
bool is_relative = (_get_global_positioning_type() == Relative);
if (axis == E)
is_relative |= (_get_e_local_positioning_type() == Relative);
if (lineG1.has(Slic3r::Axis(axis)))
{
float ret = lineG1.value(Slic3r::Axis(axis)) * lengthsScaleFactor;
return is_relative ? current_absolute_position + ret : ret + current_origin;
}
else
return current_absolute_position;
};
// updates axes positions from line
EUnits units = _get_units();
float new_pos[Num_Axis];
for (unsigned char a = X; a < Num_Axis; ++a)
{
bool is_relative = (_get_global_positioning_type() == Relative);
if (a == E)
is_relative |= (_get_e_local_positioning_type() == Relative);
new_pos[a] = axis_absolute_position_from_G1_line((EAxis)a, line, units, is_relative, _get_axis_position((EAxis)a));
new_pos[a] = axis_absolute_position((EAxis)a, line);
}
// updates feedrate from line, if present
@ -424,25 +426,25 @@ void GCodeAnalyzer::_processG92(const GCodeReader::GCodeLine& line)
if (line.has_x())
{
_set_axis_position(X, line.x() * lengthsScaleFactor);
_set_axis_origin(X, _get_axis_position(X) - line.x() * lengthsScaleFactor);
anyFound = true;
}
if (line.has_y())
{
_set_axis_position(Y, line.y() * lengthsScaleFactor);
_set_axis_origin(Y, _get_axis_position(Y) - line.y() * lengthsScaleFactor);
anyFound = true;
}
if (line.has_z())
{
_set_axis_position(Z, line.z() * lengthsScaleFactor);
_set_axis_origin(Z, _get_axis_position(Z) - line.z() * lengthsScaleFactor);
anyFound = true;
}
if (line.has_e())
{
_set_axis_position(E, line.e() * lengthsScaleFactor);
_set_axis_origin(E, _get_axis_position(E) - line.e() * lengthsScaleFactor);
anyFound = true;
}
@ -450,7 +452,7 @@ void GCodeAnalyzer::_processG92(const GCodeReader::GCodeLine& line)
{
for (unsigned char a = X; a < Num_Axis; ++a)
{
_set_axis_position((EAxis)a, 0.0f);
_set_axis_origin((EAxis)a, _get_axis_position((EAxis)a));
}
}
}
@ -781,11 +783,26 @@ float GCodeAnalyzer::_get_axis_position(EAxis axis) const
return m_state.position[axis];
}
void GCodeAnalyzer::_set_axis_origin(EAxis axis, float position)
{
m_state.origin[axis] = position;
}
float GCodeAnalyzer::_get_axis_origin(EAxis axis) const
{
return m_state.origin[axis];
}
void GCodeAnalyzer::_reset_axes_position()
{
::memset((void*)m_state.position, 0, Num_Axis * sizeof(float));
}
void GCodeAnalyzer::_reset_axes_origin()
{
::memset((void*)m_state.origin, 0, Num_Axis * sizeof(float));
}
void GCodeAnalyzer::_set_start_position(const Vec3d& position)
{
m_state.start_position = position;

View File

@ -101,6 +101,7 @@ private:
float cached_position[5];
float start_extrusion;
float position[Num_Axis];
float origin[Num_Axis];
unsigned int cur_cp_color_id = 0;
};
@ -246,8 +247,13 @@ private:
void _set_axis_position(EAxis axis, float position);
float _get_axis_position(EAxis axis) const;
void _set_axis_origin(EAxis axis, float position);
float _get_axis_origin(EAxis axis) const;
// Sets axes position to zero
void _reset_axes_position();
// Sets origin position to zero
void _reset_axes_origin();
void _set_start_position(const Vec3d& position);
const Vec3d& _get_start_position() const;

View File

@ -318,12 +318,15 @@ namespace Slic3r {
assert((g1_line_id >= (int)data->g1_line_ids.size()) || (data->g1_line_ids[g1_line_id].first >= g1_lines_count));
const Block* block = nullptr;
const G1LineIdToBlockId& map_item = data->g1_line_ids[g1_line_id];
if ((g1_line_id < (int)data->g1_line_ids.size()) && (map_item.first == g1_lines_count))
if (g1_line_id < (int)data->g1_line_ids.size())
{
if (line.has_e() && (map_item.second < (unsigned int)data->blocks.size()))
block = &data->blocks[map_item.second];
++g1_line_id;
const G1LineIdToBlockId& map_item = data->g1_line_ids[g1_line_id];
if (map_item.first == g1_lines_count)
{
if (line.has_e() && (map_item.second < (unsigned int)data->blocks.size()))
block = &data->blocks[map_item.second];
++g1_line_id;
}
}
if ((block != nullptr) && (block->elapsed_time != -1.0f))
@ -412,6 +415,11 @@ namespace Slic3r {
m_state.axis[axis].position = position;
}
void GCodeTimeEstimator::set_axis_origin(EAxis axis, float position)
{
m_state.axis[axis].origin = position;
}
void GCodeTimeEstimator::set_axis_max_feedrate(EAxis axis, float feedrate_mm_sec)
{
m_state.axis[axis].max_feedrate = feedrate_mm_sec;
@ -432,6 +440,11 @@ namespace Slic3r {
return m_state.axis[axis].position;
}
float GCodeTimeEstimator::get_axis_origin(EAxis axis) const
{
return m_state.axis[axis].origin;
}
float GCodeTimeEstimator::get_axis_max_feedrate(EAxis axis) const
{
return m_state.axis[axis].max_feedrate;
@ -758,6 +771,10 @@ namespace Slic3r {
set_axis_position(X, 0.0f);
set_axis_position(Y, 0.0f);
set_axis_position(Z, 0.0f);
set_axis_origin(X, 0.0f);
set_axis_origin(Y, 0.0f);
set_axis_origin(Z, 0.0f);
if (get_e_local_positioning_type() == Absolute)
set_axis_position(E, 0.0f);
@ -954,34 +971,35 @@ namespace Slic3r {
}
}
// Returns the new absolute position on the given axis in dependence of the given parameters
float axis_absolute_position_from_G1_line(GCodeTimeEstimator::EAxis axis, const GCodeReader::GCodeLine& lineG1, GCodeTimeEstimator::EUnits units, bool is_relative, float current_absolute_position)
{
float lengthsScaleFactor = (units == GCodeTimeEstimator::Inches) ? INCHES_TO_MM : 1.0f;
if (lineG1.has(Slic3r::Axis(axis)))
{
float ret = lineG1.value(Slic3r::Axis(axis)) * lengthsScaleFactor;
return is_relative ? current_absolute_position + ret : ret;
}
else
return current_absolute_position;
}
void GCodeTimeEstimator::_processG1(const GCodeReader::GCodeLine& line)
{
auto axis_absolute_position = [this](GCodeTimeEstimator::EAxis axis, const GCodeReader::GCodeLine& lineG1) -> float
{
float current_absolute_position = get_axis_position(axis);
float current_origin = get_axis_origin(axis);
float lengthsScaleFactor = (get_units() == GCodeTimeEstimator::Inches) ? INCHES_TO_MM : 1.0f;
bool is_relative = (get_global_positioning_type() == Relative);
if (axis == E)
is_relative |= (get_e_local_positioning_type() == Relative);
if (lineG1.has(Slic3r::Axis(axis)))
{
float ret = lineG1.value(Slic3r::Axis(axis)) * lengthsScaleFactor;
return is_relative ? current_absolute_position + ret : ret + current_origin;
}
else
return current_absolute_position;
};
PROFILE_FUNC();
increment_g1_line_id();
// updates axes positions from line
EUnits units = get_units();
float new_pos[Num_Axis];
for (unsigned char a = X; a < Num_Axis; ++a)
{
bool is_relative = (get_global_positioning_type() == Relative);
if (a == E)
is_relative |= (get_e_local_positioning_type() == Relative);
new_pos[a] = axis_absolute_position_from_G1_line((EAxis)a, line, units, is_relative, get_axis_position((EAxis)a));
new_pos[a] = axis_absolute_position((EAxis)a, line);
}
// updates feedrate from line, if present
@ -1225,25 +1243,25 @@ namespace Slic3r {
if (line.has_x())
{
set_axis_position(X, line.x() * lengthsScaleFactor);
set_axis_origin(X, get_axis_position(X) - line.x() * lengthsScaleFactor);
anyFound = true;
}
if (line.has_y())
{
set_axis_position(Y, line.y() * lengthsScaleFactor);
set_axis_origin(Y, get_axis_position(Y) - line.y() * lengthsScaleFactor);
anyFound = true;
}
if (line.has_z())
{
set_axis_position(Z, line.z() * lengthsScaleFactor);
set_axis_origin(Z, get_axis_position(Z) - line.z() * lengthsScaleFactor);
anyFound = true;
}
if (line.has_e())
{
set_axis_position(E, line.e() * lengthsScaleFactor);
set_axis_origin(E, get_axis_position(E) - line.e() * lengthsScaleFactor);
anyFound = true;
}
else
@ -1253,7 +1271,7 @@ namespace Slic3r {
{
for (unsigned char a = X; a < Num_Axis; ++a)
{
set_axis_position((EAxis)a, 0.0f);
set_axis_origin((EAxis)a, get_axis_position((EAxis)a));
}
}
}

View File

@ -55,6 +55,7 @@ namespace Slic3r {
struct Axis
{
float position; // mm
float origin; // mm
float max_feedrate; // mm/s
float max_acceleration; // mm/s^2
float max_jerk; // mm/s
@ -282,6 +283,8 @@ namespace Slic3r {
// Set current position on the given axis with the given value
void set_axis_position(EAxis axis, float position);
// Set current origin on the given axis with the given value
void set_axis_origin(EAxis axis, float position);
void set_axis_max_feedrate(EAxis axis, float feedrate_mm_sec);
void set_axis_max_acceleration(EAxis axis, float acceleration);
@ -289,6 +292,8 @@ namespace Slic3r {
// Returns current position on the given axis
float get_axis_position(EAxis axis) const;
// Returns current origin on the given axis
float get_axis_origin(EAxis axis) const;
float get_axis_max_feedrate(EAxis axis) const;
float get_axis_max_acceleration(EAxis axis) const;

View File

@ -70,7 +70,7 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollec
fill_surfaces
);
if (this->layer()->lower_layer != NULL)
if (this->layer()->lower_layer != nullptr)
// Cummulative sum of polygons over all the regions.
g.lower_slices = &this->layer()->lower_layer->slices;
@ -130,7 +130,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
bridges.emplace_back(surface);
}
if (surface.is_internal()) {
assert(surface.surface_type == stInternal);
assert(surface.surface_type == stInternal || surface.surface_type == stInternalSolid);
if (! has_infill && lower_layer != nullptr)
polygons_append(voids, surface.expolygon);
internal.emplace_back(std::move(surface));

View File

@ -433,6 +433,7 @@ void PrintConfigDef::init_fff_params()
"If left zero, default extrusion width will be used if set, otherwise 1.125 x nozzle diameter will be used. "
"If expressed as percentage (for example 200%), it will be computed over layer height.");
def->sidetext = L("mm or %");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloatOrPercent(0, false));
@ -541,6 +542,7 @@ void PrintConfigDef::init_fff_params()
"(see the tooltips for perimeter extrusion width, infill extrusion width etc). "
"If expressed as percentage (for example: 230%), it will be computed over layer height.");
def->sidetext = L("mm or %");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloatOrPercent(0, false));
@ -863,6 +865,7 @@ void PrintConfigDef::init_fff_params()
"If set to zero, it will use the default extrusion width.");
def->sidetext = L("mm or %");
def->ratio_over = "first_layer_height";
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloatOrPercent(200, true));
@ -994,6 +997,7 @@ void PrintConfigDef::init_fff_params()
"You may want to use fatter extrudates to speed up the infill and make your parts stronger. "
"If expressed as percentage (for example 90%) it will be computed over layer height.");
def->sidetext = L("mm or %");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloatOrPercent(0, false));
@ -1406,6 +1410,7 @@ void PrintConfigDef::init_fff_params()
"If expressed as percentage (for example 200%) it will be computed over layer height.");
def->sidetext = L("mm or %");
def->aliases = { "perimeters_extrusion_width" };
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloatOrPercent(0, false));
@ -1743,6 +1748,7 @@ void PrintConfigDef::init_fff_params()
"If left zero, default extrusion width will be used if set, otherwise 1.125 x nozzle diameter will be used. "
"If expressed as percentage (for example 90%) it will be computed over layer height.");
def->sidetext = L("mm or %");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloatOrPercent(0, false));
@ -1917,6 +1923,7 @@ void PrintConfigDef::init_fff_params()
"If left zero, default extrusion width will be used if set, otherwise nozzle diameter will be used. "
"If expressed as percentage (for example 90%) it will be computed over layer height.");
def->sidetext = L("mm or %");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloatOrPercent(0, false));
@ -2076,6 +2083,7 @@ void PrintConfigDef::init_fff_params()
"If left zero, default extrusion width will be used if set, otherwise nozzle diameter will be used. "
"If expressed as percentage (for example 90%) it will be computed over layer height.");
def->sidetext = L("mm or %");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloatOrPercent(0, false));
@ -2898,9 +2906,13 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
const PrintConfigDef print_config_def;
DynamicPrintConfig* DynamicPrintConfig::new_from_defaults()
DynamicPrintConfig DynamicPrintConfig::full_print_config()
{
return DynamicPrintConfig((const PrintRegionConfig&)FullPrintConfig::defaults());
}
DynamicPrintConfig::DynamicPrintConfig(const StaticPrintConfig& rhs) : DynamicConfig(rhs, rhs.keys_ref())
{
return new_from_defaults_keys(FullPrintConfig::defaults().keys());
}
DynamicPrintConfig* DynamicPrintConfig::new_from_defaults_keys(const std::vector<std::string> &keys)

View File

@ -214,6 +214,8 @@ private:
// This definition is constant.
extern const PrintConfigDef print_config_def;
class StaticPrintConfig;
// Slic3r dynamic configuration, used to override the configuration
// per object, per modification volume or per printing material.
// The dynamic configuration is also used to store user modifications of the print global parameters,
@ -224,9 +226,11 @@ class DynamicPrintConfig : public DynamicConfig
{
public:
DynamicPrintConfig() {}
DynamicPrintConfig(const DynamicPrintConfig &other) : DynamicConfig(other) {}
DynamicPrintConfig(const DynamicPrintConfig &rhs) : DynamicConfig(rhs) {}
explicit DynamicPrintConfig(const StaticPrintConfig &rhs);
explicit DynamicPrintConfig(const ConfigBase &rhs) : DynamicConfig(rhs) {}
static DynamicPrintConfig* new_from_defaults();
static DynamicPrintConfig full_print_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.
@ -262,6 +266,8 @@ public:
// Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here.
const ConfigDef* def() const override { return &print_config_def; }
// Reference to the cached list of keys.
virtual const t_config_option_keys& keys_ref() const = 0;
protected:
// Verify whether the opt_key has not been obsoleted or renamed.
@ -350,6 +356,7 @@ public: \
{ return s_cache_##CLASS_NAME.optptr(opt_key, this); } \
/* Overrides ConfigBase::keys(). Collect names of all configuration values maintained by this configuration store. */ \
t_config_option_keys keys() const override { return s_cache_##CLASS_NAME.keys(); } \
const t_config_option_keys& keys_ref() const override { return s_cache_##CLASS_NAME.keys(); } \
static const CLASS_NAME& defaults() { initialize_cache(); return s_cache_##CLASS_NAME.defaults(); } \
private: \
static void initialize_cache() \

Binary file not shown.

View File

@ -0,0 +1 @@
filament_colour = #ABCD

View File

@ -3,9 +3,16 @@ add_executable(${_TEST_NAME}_tests
${_TEST_NAME}_tests.cpp
test_data.cpp
test_data.hpp
test_extrusion_entity.cpp
test_fill.cpp
test_flow.cpp
test_gcodewriter.cpp
test_model.cpp
test_print.cpp
test_printgcode.cpp
test_printobject.cpp
test_skirt_brim.cpp
test_support_material.cpp
test_trianglemesh.cpp
)
target_link_libraries(${_TEST_NAME}_tests test_common libslic3r)

File diff suppressed because one or more lines are too long

View File

@ -1,12 +1,12 @@
#ifndef SLIC3R_TEST_DATA_HPP
#define SLIC3R_TEST_DATA_HPP
#include "libslic3r/Point.hpp"
#include "libslic3r/TriangleMesh.hpp"
#include "libslic3r/Config.hpp"
#include "libslic3r/Geometry.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/Point.hpp"
#include "libslic3r/Print.hpp"
#include "libslic3r/Config.hpp"
#include "libslic3r/TriangleMesh.hpp"
#include <unordered_map>
@ -61,15 +61,24 @@ bool _equiv(const T& a, const T& b) { return std::abs(a - b) < EPSILON; }
template <typename T>
bool _equiv(const T& a, const T& b, double epsilon) { return abs(a - b) < epsilon; }
//Slic3r::Model model(const std::string& model_name, TestMesh m, Vec3d translate = Vec3d(0,0,0), Vec3d scale = Vec3d(1.0,1.0,1.0));
//Slic3r::Model model(const std::string& model_name, TestMesh m, Vec3d translate = Vec3d(0,0,0), double scale = 1.0);
Slic3r::Model model(const std::string& model_name, TriangleMesh&& _mesh);
void init_print(std::vector<TriangleMesh> &&meshes, Slic3r::Print &print, Slic3r::Model& model, const DynamicPrintConfig &config_in, bool comments = false);
void init_print(std::initializer_list<TestMesh> meshes, Slic3r::Print &print, Slic3r::Model& model, const Slic3r::DynamicPrintConfig &config_in = Slic3r::DynamicPrintConfig::full_print_config(), bool comments = false);
void init_print(std::initializer_list<TriangleMesh> meshes, Slic3r::Print &print, Slic3r::Model& model, const Slic3r::DynamicPrintConfig &config_in = Slic3r::DynamicPrintConfig::full_print_config(), bool comments = false);
void init_print(std::initializer_list<TestMesh> meshes, Slic3r::Print &print, Slic3r::Model& model, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items, bool comments = false);
void init_print(std::initializer_list<TriangleMesh> meshes, Slic3r::Print &print, Slic3r::Model& model, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items, bool comments = false);
std::shared_ptr<Print> init_print(std::initializer_list<TestMesh> meshes, Slic3r::Model& model, std::shared_ptr<Slic3r::DynamicPrintConfig> _config = std::shared_ptr<Slic3r::DynamicPrintConfig>(Slic3r::DynamicPrintConfig::new_from_defaults()), bool comments = false);
std::shared_ptr<Print> init_print(std::initializer_list<TriangleMesh> meshes, Slic3r::Model& model, std::shared_ptr<Slic3r::DynamicPrintConfig> _config = std::shared_ptr<Slic3r::DynamicPrintConfig>(Slic3r::DynamicPrintConfig::new_from_defaults()), bool comments = false);
void init_and_process_print(std::initializer_list<TestMesh> meshes, Slic3r::Print &print, const DynamicPrintConfig& config, bool comments = false);
void init_and_process_print(std::initializer_list<TriangleMesh> meshes, Slic3r::Print &print, const DynamicPrintConfig& config, bool comments = false);
void init_and_process_print(std::initializer_list<TestMesh> meshes, Slic3r::Print &print, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items, bool comments = false);
void init_and_process_print(std::initializer_list<TriangleMesh> meshes, Slic3r::Print &print, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items, bool comments = false);
std::string gcode(std::shared_ptr<Print> print);
std::string gcode(Print& print);
std::string slice(std::initializer_list<TestMesh> meshes, const DynamicPrintConfig &config, bool comments = false);
std::string slice(std::initializer_list<TriangleMesh> meshes, const DynamicPrintConfig &config, bool comments = false);
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);
} } // namespace Slic3r::Test

View File

@ -0,0 +1,85 @@
#include <catch2/catch.hpp>
#include <cstdlib>
#include "libslic3r/ExtrusionEntityCollection.hpp"
#include "libslic3r/ExtrusionEntity.hpp"
#include "libslic3r/Point.hpp"
#include "libslic3r/libslic3r.h"
#include "test_data.hpp"
using namespace Slic3r;
static inline Slic3r::Point random_point(float LO=-50, float HI=50)
{
Vec2f pt = Vec2f(LO, LO) + (Vec2d(rand(), rand()) * (HI-LO) / RAND_MAX).cast<float>();
return pt.cast<coord_t>();
}
// build a sample extrusion entity collection with random start and end points.
static Slic3r::ExtrusionPath random_path(size_t length = 20, float LO = -50, float HI = 50)
{
ExtrusionPath t {erPerimeter, 1.0, 1.0, 1.0};
for (size_t j = 0; j < length; ++ j)
t.polyline.append(random_point(LO, HI));
return t;
}
static Slic3r::ExtrusionPaths random_paths(size_t count = 10, size_t length = 20, float LO = -50, float HI = 50)
{
Slic3r::ExtrusionPaths p;
for (size_t i = 0; i < count; ++ i)
p.push_back(random_path(length, LO, HI));
return p;
}
SCENARIO("ExtrusionEntityCollection: Polygon flattening", "[ExtrusionEntity]") {
srand(0xDEADBEEF); // consistent seed for test reproducibility.
// Generate one specific random path set and save it for later comparison
Slic3r::ExtrusionPaths nosort_path_set = random_paths();
Slic3r::ExtrusionEntityCollection sub_nosort;
sub_nosort.append(nosort_path_set);
sub_nosort.no_sort = true;
Slic3r::ExtrusionEntityCollection sub_sort;
sub_sort.no_sort = false;
sub_sort.append(random_paths());
GIVEN("A Extrusion Entity Collection with a child that has one child that is marked as no-sort") {
Slic3r::ExtrusionEntityCollection sample;
Slic3r::ExtrusionEntityCollection output;
sample.append(sub_sort);
sample.append(sub_nosort);
sample.append(sub_sort);
WHEN("The EEC is flattened with default options (preserve_order=false)") {
output = sample.flatten();
THEN("The output EEC contains no Extrusion Entity Collections") {
CHECK(std::count_if(output.entities.cbegin(), output.entities.cend(), [=](const ExtrusionEntity* e) {return e->is_collection();}) == 0);
}
}
WHEN("The EEC is flattened with preservation (preserve_order=true)") {
output = sample.flatten(true);
THEN("The output EECs contains one EEC.") {
CHECK(std::count_if(output.entities.cbegin(), output.entities.cend(), [=](const ExtrusionEntity* e) {return e->is_collection();}) == 1);
}
AND_THEN("The ordered EEC contains the same order of elements than the original") {
// find the entity in the collection
for (auto e : output.entities)
if (e->is_collection()) {
ExtrusionEntityCollection *temp = dynamic_cast<ExtrusionEntityCollection*>(e);
// check each Extrusion path against nosort_path_set to see if the first and last match the same
CHECK(nosort_path_set.size() == temp->entities.size());
for (size_t i = 0; i < nosort_path_set.size(); ++ i) {
CHECK(temp->entities[i]->first_point() == nosort_path_set[i].first_point());
CHECK(temp->entities[i]->last_point() == nosort_path_set[i].last_point());
}
}
}
}
}
}

View File

@ -0,0 +1,474 @@
#include <catch2/catch.hpp>
#include <numeric>
#include <sstream>
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/Fill/Fill.hpp"
#include "libslic3r/Flow.hpp"
#include "libslic3r/Geometry.hpp"
#include "libslic3r/Print.hpp"
#include "libslic3r/SVG.hpp"
#include "libslic3r/libslic3r.h"
#include "test_data.hpp"
using namespace Slic3r;
bool test_if_solid_surface_filled(const ExPolygon& expolygon, double flow_spacing, double angle = 0, double density = 1.0);
#if 0
TEST_CASE("Fill: adjusted solid distance") {
int surface_width = 250;
int distance = Slic3r::Flow::solid_spacing(surface_width, 47);
REQUIRE(distance == Approx(50));
REQUIRE(surface_width % distance == 0);
}
#endif
TEST_CASE("Fill: Pattern Path Length", "[Fill]") {
std::unique_ptr<Slic3r::Fill> filler(Slic3r::Fill::new_from_type("rectilinear"));
filler->angle = float(-(PI)/2.0);
FillParams fill_params;
filler->spacing = 5;
fill_params.dont_adjust = true;
//fill_params.endpoints_overlap = false;
fill_params.density = float(filler->spacing / 50.0);
auto test = [&filler, &fill_params] (const ExPolygon& poly) -> Slic3r::Polylines {
Slic3r::Surface surface(stTop, poly);
return filler->fill_surface(&surface, fill_params);
};
SECTION("Square") {
Slic3r::Points test_set;
test_set.reserve(4);
std::vector<Vec2d> points {Vec2d(0,0), Vec2d(100,0), Vec2d(100,100), Vec2d(0,100)};
for (size_t i = 0; i < 4; ++i) {
std::transform(points.cbegin()+i, points.cend(), std::back_inserter(test_set), [] (const Vec2d& a) -> Point { return Point::new_scale(a.x(), a.y()); } );
std::transform(points.cbegin(), points.cbegin()+i, std::back_inserter(test_set), [] (const Vec2d& a) -> Point { return Point::new_scale(a.x(), a.y()); } );
Slic3r::Polylines paths = test(Slic3r::ExPolygon(test_set));
REQUIRE(paths.size() == 1); // one continuous path
// TODO: determine what the "Expected length" should be for rectilinear fill of a 100x100 polygon.
// This check only checks that it's above scale(3*100 + 2*50) + scaled_epsilon.
// ok abs($paths->[0]->length - scale(3*100 + 2*50)) - scaled_epsilon, 'path has expected length';
REQUIRE(std::abs(paths[0].length() - static_cast<double>(scale_(3*100 + 2*50))) - SCALED_EPSILON > 0); // path has expected length
test_set.clear();
}
}
SECTION("Diamond with endpoints on grid") {
std::vector<Vec2d> points {Vec2d(0,0), Vec2d(100,0), Vec2d(150,50), Vec2d(100,100), Vec2d(0,100), Vec2d(-50,50)};
Slic3r::Points test_set;
test_set.reserve(6);
std::transform(points.cbegin(), points.cend(), std::back_inserter(test_set), [] (const Vec2d& a) -> Point { return Point::new_scale(a.x(), a.y()); } );
Slic3r::Polylines paths = test(Slic3r::ExPolygon(test_set));
REQUIRE(paths.size() == 1); // one continuous path
}
SECTION("Square with hole") {
std::vector<Vec2d> square {Vec2d(0,0), Vec2d(100,0), Vec2d(100,100), Vec2d(0,100)};
std::vector<Vec2d> hole {Vec2d(25,25), Vec2d(75,25), Vec2d(75,75), Vec2d(25,75) };
std::reverse(hole.begin(), hole.end());
Slic3r::Points test_hole;
Slic3r::Points test_square;
std::transform(square.cbegin(), square.cend(), std::back_inserter(test_square), [] (const Vec2d& a) -> Point { return Point::new_scale(a.x(), a.y()); } );
std::transform(hole.cbegin(), hole.cend(), std::back_inserter(test_hole), [] (const Vec2d& a) -> Point { return Point::new_scale(a.x(), a.y()); } );
for (double angle : {-(PI/2.0), -(PI/4.0), -(PI), PI/2.0, PI}) {
for (double spacing : {25.0, 5.0, 7.5, 8.5}) {
fill_params.density = float(filler->spacing / spacing);
filler->angle = float(angle);
ExPolygon e(test_square, test_hole);
Slic3r::Polylines paths = test(e);
#if 0
{
BoundingBox bbox = get_extents(e);
SVG svg("c:\\data\\temp\\square_with_holes.svg", bbox);
svg.draw(e);
svg.draw(paths);
svg.Close();
}
#endif
REQUIRE((paths.size() >= 1 && paths.size() <= 3));
// paths don't cross hole
REQUIRE(diff_pl(paths, offset(e, float(SCALED_EPSILON*10))).size() == 0);
}
}
}
SECTION("Regression: Missing infill segments in some rare circumstances") {
filler->angle = float(PI/4.0);
fill_params.dont_adjust = false;
filler->spacing = 0.654498;
//filler->endpoints_overlap = unscale(359974);
fill_params.density = 1;
filler->layer_id = 66;
filler->z = 20.15;
Slic3r::Points points {Point(25771516,14142125),Point(14142138,25771515),Point(2512749,14142131),Point(14142125,2512749)};
Slic3r::Polylines paths = test(Slic3r::ExPolygon(points));
REQUIRE(paths.size() == 1); // one continuous path
// TODO: determine what the "Expected length" should be for rectilinear fill of a 100x100 polygon.
// This check only checks that it's above scale(3*100 + 2*50) + scaled_epsilon.
// ok abs($paths->[0]->length - scale(3*100 + 2*50)) - scaled_epsilon, 'path has expected length';
REQUIRE(std::abs(paths[0].length() - static_cast<double>(scale_(3*100 + 2*50))) - SCALED_EPSILON > 0); // path has expected length
}
SECTION("Rotated Square") {
Slic3r::Points square { Point::new_scale(0,0), Point::new_scale(50,0), Point::new_scale(50,50), Point::new_scale(0,50)};
Slic3r::ExPolygon expolygon(square);
std::unique_ptr<Slic3r::Fill> filler(Slic3r::Fill::new_from_type("rectilinear"));
filler->bounding_box = get_extents(expolygon.contour);
filler->angle = 0;
Surface surface(stTop, expolygon);
auto flow = Slic3r::Flow(0.69, 0.4, 0.50);
FillParams fill_params;
fill_params.density = 1.0;
filler->spacing = flow.spacing();
for (auto angle : { 0.0, 45.0}) {
surface.expolygon.rotate(angle, Point(0,0));
Polylines paths = filler->fill_surface(&surface, fill_params);
REQUIRE(paths.size() == 1);
}
}
SECTION("Solid surface fill") {
Slic3r::Points points {
Point::new_scale(6883102, 9598327.01296997),
Point::new_scale(6883102, 20327272.01297),
Point::new_scale(3116896, 20327272.01297),
Point::new_scale(3116896, 9598327.01296997)
};
Slic3r::ExPolygon expolygon(points);
REQUIRE(test_if_solid_surface_filled(expolygon, 0.55) == true);
for (size_t i = 0; i <= 20; ++i)
{
expolygon.scale(1.05);
REQUIRE(test_if_solid_surface_filled(expolygon, 0.55) == true);
}
}
SECTION("Solid surface fill") {
Slic3r::Points points {
Slic3r::Point(59515297,5422499),Slic3r::Point(59531249,5578697),Slic3r::Point(59695801,6123186),
Slic3r::Point(59965713,6630228),Slic3r::Point(60328214,7070685),Slic3r::Point(60773285,7434379),
Slic3r::Point(61274561,7702115),Slic3r::Point(61819378,7866770),Slic3r::Point(62390306,7924789),
Slic3r::Point(62958700,7866744),Slic3r::Point(63503012,7702244),Slic3r::Point(64007365,7434357),
Slic3r::Point(64449960,7070398),Slic3r::Point(64809327,6634999),Slic3r::Point(65082143,6123325),
Slic3r::Point(65245005,5584454),Slic3r::Point(65266967,5422499),Slic3r::Point(66267307,5422499),
Slic3r::Point(66269190,8310081),Slic3r::Point(66275379,17810072),Slic3r::Point(66277259,20697500),
Slic3r::Point(65267237,20697500),Slic3r::Point(65245004,20533538),Slic3r::Point(65082082,19994444),
Slic3r::Point(64811462,19488579),Slic3r::Point(64450624,19048208),Slic3r::Point(64012101,18686514),
Slic3r::Point(63503122,18415781),Slic3r::Point(62959151,18251378),Slic3r::Point(62453416,18198442),
Slic3r::Point(62390147,18197355),Slic3r::Point(62200087,18200576),Slic3r::Point(61813519,18252990),
Slic3r::Point(61274433,18415918),Slic3r::Point(60768598,18686517),Slic3r::Point(60327567,19047892),
Slic3r::Point(59963609,19493297),Slic3r::Point(59695865,19994587),Slic3r::Point(59531222,20539379),
Slic3r::Point(59515153,20697500),Slic3r::Point(58502480,20697500),Slic3r::Point(58502480,5422499)
};
Slic3r::ExPolygon expolygon(points);
REQUIRE(test_if_solid_surface_filled(expolygon, 0.55) == true);
REQUIRE(test_if_solid_surface_filled(expolygon, 0.55, PI/2.0) == true);
}
SECTION("Solid surface fill") {
Slic3r::Points points {
Point::new_scale(0,0),Point::new_scale(98,0),Point::new_scale(98,10), Point::new_scale(0,10)
};
Slic3r::ExPolygon expolygon(points);
REQUIRE(test_if_solid_surface_filled(expolygon, 0.5, 45.0, 0.99) == true);
}
}
/*
{
my $collection = Slic3r::Polyline::Collection->new(
Slic3r::Polyline->new([0,15], [0,18], [0,20]),
Slic3r::Polyline->new([0,10], [0,8], [0,5]),
);
is_deeply
[ map $_->[Y], map @$_, @{$collection->chained_path_from(Slic3r::Point->new(0,30), 0)} ],
[20, 18, 15, 10, 8, 5],
'chained path';
}
{
my $collection = Slic3r::Polyline::Collection->new(
Slic3r::Polyline->new([4,0], [10,0], [15,0]),
Slic3r::Polyline->new([10,5], [15,5], [20,5]),
);
is_deeply
[ map $_->[X], map @$_, @{$collection->chained_path_from(Slic3r::Point->new(30,0), 0)} ],
[reverse 4, 10, 15, 10, 15, 20],
'chained path';
}
{
my $collection = Slic3r::ExtrusionPath::Collection->new(
map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1),
Slic3r::Polyline->new([0,15], [0,18], [0,20]),
Slic3r::Polyline->new([0,10], [0,8], [0,5]),
);
is_deeply
[ map $_->[Y], map @{$_->polyline}, @{$collection->chained_path_from(Slic3r::Point->new(0,30), 0)} ],
[20, 18, 15, 10, 8, 5],
'chained path';
}
{
my $collection = Slic3r::ExtrusionPath::Collection->new(
map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1),
Slic3r::Polyline->new([15,0], [10,0], [4,0]),
Slic3r::Polyline->new([10,5], [15,5], [20,5]),
);
is_deeply
[ map $_->[X], map @{$_->polyline}, @{$collection->chained_path_from(Slic3r::Point->new(30,0), 0)} ],
[reverse 4, 10, 15, 10, 15, 20],
'chained path';
}
for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) {
my $config = Slic3r::Config->new_from_defaults;
$config->set('fill_pattern', $pattern);
$config->set('external_fill_pattern', $pattern);
$config->set('perimeters', 1);
$config->set('skirts', 0);
$config->set('fill_density', 20);
$config->set('layer_height', 0.05);
$config->set('perimeter_extruder', 1);
$config->set('infill_extruder', 2);
my $print = Slic3r::Test::init_print('20mm_cube', config => $config, scale => 2);
ok my $gcode = Slic3r::Test::gcode($print), "successful $pattern infill generation";
my $tool = undef;
my @perimeter_points = my @infill_points = ();
Slic3r::GCode::Reader->new->parse($gcode, sub {
my ($self, $cmd, $args, $info) = @_;
if ($cmd =~ /^T(\d+)/) {
$tool = $1;
} elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) {
if ($tool == $config->perimeter_extruder-1) {
push @perimeter_points, Slic3r::Point->new_scale($args->{X}, $args->{Y});
} elsif ($tool == $config->infill_extruder-1) {
push @infill_points, Slic3r::Point->new_scale($args->{X}, $args->{Y});
}
}
});
my $convex_hull = convex_hull(\@perimeter_points);
ok !(defined first { !$convex_hull->contains_point($_) } @infill_points), "infill does not exceed perimeters ($pattern)";
}
{
my $config = Slic3r::Config->new_from_defaults;
$config->set('infill_only_where_needed', 1);
$config->set('bottom_solid_layers', 0);
$config->set('infill_extruder', 2);
$config->set('infill_extrusion_width', 0.5);
$config->set('fill_density', 40);
$config->set('cooling', 0); # for preventing speeds from being altered
$config->set('first_layer_speed', '100%'); # for preventing speeds from being altered
my $test = sub {
my $print = Slic3r::Test::init_print('pyramid', config => $config);
my $tool = undef;
my @infill_extrusions = (); # array of polylines
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
my ($self, $cmd, $args, $info) = @_;
if ($cmd =~ /^T(\d+)/) {
$tool = $1;
} elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) {
if ($tool == $config->infill_extruder-1) {
push @infill_extrusions, Slic3r::Line->new_scale(
[ $self->X, $self->Y ],
[ $info->{new_X}, $info->{new_Y} ],
);
}
}
});
return 0 if !@infill_extrusions; # prevent calling convex_hull() with no points
my $convex_hull = convex_hull([ map $_->pp, map @$_, @infill_extrusions ]);
return unscale unscale sum(map $_->area, @{offset([$convex_hull], scale(+$config->infill_extrusion_width/2))});
};
my $tolerance = 5; # mm^2
$config->set('solid_infill_below_area', 0);
ok $test->() < $tolerance,
'no infill is generated when using infill_only_where_needed on a pyramid';
$config->set('solid_infill_below_area', 70);
ok abs($test->() - $config->solid_infill_below_area) < $tolerance,
'infill is only generated under the forced solid shells';
}
{
my $config = Slic3r::Config->new_from_defaults;
$config->set('skirts', 0);
$config->set('perimeters', 1);
$config->set('fill_density', 0);
$config->set('top_solid_layers', 0);
$config->set('bottom_solid_layers', 0);
$config->set('solid_infill_below_area', 20000000);
$config->set('solid_infill_every_layers', 2);
$config->set('perimeter_speed', 99);
$config->set('external_perimeter_speed', 99);
$config->set('cooling', 0);
$config->set('first_layer_speed', '100%');
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
my %layers_with_extrusion = ();
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
my ($self, $cmd, $args, $info) = @_;
if ($cmd eq 'G1' && $info->{dist_XY} > 0 && $info->{extruding}) {
if (($args->{F} // $self->F) != $config->perimeter_speed*60) {
$layers_with_extrusion{$self->Z} = ($args->{F} // $self->F);
}
}
});
ok !%layers_with_extrusion,
"solid_infill_below_area and solid_infill_every_layers are ignored when fill_density is 0";
}
{
my $config = Slic3r::Config->new_from_defaults;
$config->set('skirts', 0);
$config->set('perimeters', 3);
$config->set('fill_density', 0);
$config->set('layer_height', 0.2);
$config->set('first_layer_height', 0.2);
$config->set('nozzle_diameter', [0.35]);
$config->set('infill_extruder', 2);
$config->set('solid_infill_extruder', 2);
$config->set('infill_extrusion_width', 0.52);
$config->set('solid_infill_extrusion_width', 0.52);
$config->set('first_layer_extrusion_width', 0);
my $print = Slic3r::Test::init_print('A', config => $config);
my %infill = (); # Z => [ Line, Line ... ]
my $tool = undef;
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
my ($self, $cmd, $args, $info) = @_;
if ($cmd =~ /^T(\d+)/) {
$tool = $1;
} elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) {
if ($tool == $config->infill_extruder-1) {
my $z = 1 * $self->Z;
$infill{$z} ||= [];
push @{$infill{$z}}, Slic3r::Line->new_scale(
[ $self->X, $self->Y ],
[ $info->{new_X}, $info->{new_Y} ],
);
}
}
});
my $grow_d = scale($config->infill_extrusion_width)/2;
my $layer0_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.2} } ]);
my $layer1_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.4} } ]);
my $diff = diff($layer0_infill, $layer1_infill);
$diff = offset2_ex($diff, -$grow_d, +$grow_d);
$diff = [ grep { $_->area > 2*(($grow_d*2)**2) } @$diff ];
is scalar(@$diff), 0, 'no missing parts in solid shell when fill_density is 0';
}
{
# GH: #2697
my $config = Slic3r::Config->new_from_defaults;
$config->set('perimeter_extrusion_width', 0.72);
$config->set('top_infill_extrusion_width', 0.1);
$config->set('infill_extruder', 2); # in order to distinguish infill
$config->set('solid_infill_extruder', 2); # in order to distinguish infill
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
my %infill = (); # Z => [ Line, Line ... ]
my %other = (); # Z => [ Line, Line ... ]
my $tool = undef;
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
my ($self, $cmd, $args, $info) = @_;
if ($cmd =~ /^T(\d+)/) {
$tool = $1;
} elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) {
my $z = 1 * $self->Z;
my $line = Slic3r::Line->new_scale(
[ $self->X, $self->Y ],
[ $info->{new_X}, $info->{new_Y} ],
);
if ($tool == $config->infill_extruder-1) {
$infill{$z} //= [];
push @{$infill{$z}}, $line;
} else {
$other{$z} //= [];
push @{$other{$z}}, $line;
}
}
});
my $top_z = max(keys %infill);
my $top_infill_grow_d = scale($config->top_infill_extrusion_width)/2;
my $top_infill = union([ map @{$_->grow($top_infill_grow_d)}, @{ $infill{$top_z} } ]);
my $perimeters_grow_d = scale($config->perimeter_extrusion_width)/2;
my $perimeters = union([ map @{$_->grow($perimeters_grow_d)}, @{ $other{$top_z} } ]);
my $covered = union_ex([ @$top_infill, @$perimeters ]);
my @holes = map @{$_->holes}, @$covered;
ok sum(map unscale unscale $_->area*-1, @holes) < 1, 'no gaps between top solid infill and perimeters';
}
*/
bool test_if_solid_surface_filled(const ExPolygon& expolygon, double flow_spacing, double angle, double density)
{
std::unique_ptr<Slic3r::Fill> filler(Slic3r::Fill::new_from_type("rectilinear"));
filler->bounding_box = get_extents(expolygon.contour);
filler->angle = float(angle);
Flow flow(flow_spacing, 0.4, flow_spacing);
filler->spacing = flow.spacing();
FillParams fill_params;
fill_params.density = float(density);
fill_params.dont_adjust = false;
Surface surface(stBottom, expolygon);
Slic3r::Polylines paths = filler->fill_surface(&surface, fill_params);
// check whether any part was left uncovered
Polygons grown_paths;
grown_paths.reserve(paths.size());
// figure out what is actually going on here re: data types
float line_offset = float(scale_(filler->spacing / 2.0 + EPSILON));
std::for_each(paths.begin(), paths.end(), [line_offset, &grown_paths] (const Slic3r::Polyline& p) {
polygons_append(grown_paths, offset(p, line_offset));
});
// Shrink the initial expolygon a bit, this simulates the infill / perimeter overlap that we usually apply.
ExPolygons uncovered = diff_ex(offset(expolygon, - float(0.2 * scale_(flow_spacing))), grown_paths, true);
// ignore very small dots
const double scaled_flow_spacing = std::pow(scale_(flow_spacing), 2);
uncovered.erase(std::remove_if(uncovered.begin(), uncovered.end(), [scaled_flow_spacing](const ExPolygon& poly) { return poly.area() < scaled_flow_spacing; }), uncovered.end());
#if 0
if (! uncovered.empty()) {
BoundingBox bbox = get_extents(expolygon.contour);
bbox.merge(get_extents(uncovered));
bbox.merge(get_extents(grown_paths));
SVG svg("c:\\data\\temp\\test_if_solid_surface_filled.svg", bbox);
svg.draw(expolygon);
svg.draw(uncovered, "red");
svg.Close();
}
#endif
return uncovered.empty(); // solid surface is fully filled
}

View File

@ -18,22 +18,25 @@ using namespace Slic3r;
SCENARIO("Extrusion width specifics", "[!mayfail]") {
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
std::shared_ptr<DynamicPrintConfig> config(Slic3r::DynamicPrintConfig::new_from_defaults());
config->opt_int("skirts") = 1;
config->opt_float("brim_width") = 2.;
config->opt_int("perimeters") = 3;
config->set_deserialize("fill_density", "40%");
config->set_deserialize("first_layer_height", "100%");
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
config.set_deserialize({
{ "brim_width", 2 },
{ "skirts", 1 },
{ "perimeters", 3 },
{ "fill_density", "40%" },
{ "first_layer_height", "100%" }
});
WHEN("first layer width set to 2mm") {
Slic3r::Model model;
config->set_deserialize("first_layer_extrusion_width", "2");
std::shared_ptr<Print> print = Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config);
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");
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

View File

@ -0,0 +1,61 @@
#include <catch2/catch.hpp>
#include "libslic3r/libslic3r.h"
#include "libslic3r/Model.hpp"
#include <boost/nowide/cstdio.hpp>
#include <boost/filesystem.hpp>
#include "test_data.hpp"
using namespace Slic3r;
using namespace Slic3r::Test;
SCENARIO("Model construction", "[Model]") {
GIVEN("A Slic3r Model") {
Slic3r::Model model;
Slic3r::TriangleMesh sample_mesh = Slic3r::make_cube(20,20,20);
Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
Slic3r::Print print;
WHEN("Model object is added") {
Slic3r::ModelObject *model_object = model.add_object();
THEN("Model object list == 1") {
REQUIRE(model.objects.size() == 1);
}
model_object->add_volume(sample_mesh);
THEN("Model volume list == 1") {
REQUIRE(model_object->volumes.size() == 1);
}
THEN("Model volume is a part") {
REQUIRE(model_object->volumes.front()->is_model_part());
}
THEN("Mesh is equivalent to input mesh.") {
REQUIRE(! sample_mesh.its.vertices.empty());
const std::vector<Vec3f>& mesh_vertices = model_object->volumes.front()->mesh().its.vertices;
Vec3f mesh_offset = model_object->volumes.front()->source.mesh_offset.cast<float>();
for (size_t i = 0; i < sample_mesh.its.vertices.size(); ++ i) {
const Vec3f &p1 = sample_mesh.its.vertices[i];
const Vec3f p2 = mesh_vertices[i] + mesh_offset;
REQUIRE((p2 - p1).norm() < EPSILON);
}
}
model_object->add_instance();
model.arrange_objects(PrintConfig::min_object_distance(&config));
model.center_instances_around_point(Slic3r::Vec2d(100, 100));
model_object->ensure_on_bed();
print.auto_assign_extruders(model_object);
THEN("Print works?") {
print.set_status_silent();
print.apply(model, config);
print.process();
boost::filesystem::path temp = boost::filesystem::unique_path();
print.export_gcode(temp.string(), nullptr);
REQUIRE(boost::filesystem::exists(temp));
REQUIRE(boost::filesystem::is_regular_file(temp));
REQUIRE(boost::filesystem::file_size(temp) > 0);
boost::nowide::remove(temp.string().c_str());
}
}
}
}

View File

@ -0,0 +1,127 @@
#include <catch2/catch.hpp>
#include "libslic3r/libslic3r.h"
#include "libslic3r/Print.hpp"
#include "test_data.hpp"
using namespace Slic3r;
using namespace Slic3r::Test;
SCENARIO("PrintObject: Perimeter generation", "[PrintObject]") {
GIVEN("20mm cube and default config") {
WHEN("make_perimeters() is called") {
Slic3r::Print print;
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, { { "fill_density", 0 } });
const PrintObject &object = *print.objects().front();
THEN("67 layers exist in the model") {
REQUIRE(object.layers().size() == 66);
}
THEN("Every layer in region 0 has 1 island of perimeters") {
for (const Layer *layer : object.layers())
REQUIRE(layer->regions().front()->perimeters.entities.size() == 1);
}
THEN("Every layer in region 0 has 3 paths in its perimeters list.") {
for (const Layer *layer : object.layers())
REQUIRE(layer->regions().front()->perimeters.items_count() == 3);
}
}
}
}
SCENARIO("Print: Skirt generation", "[Print]") {
GIVEN("20mm cube and default config") {
WHEN("Skirts is set to 2 loops") {
Slic3r::Print print;
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, {
{ "skirt_height", 1 },
{ "skirt_distance", 1 },
{ "skirts", 2 }
});
THEN("Skirt Extrusion collection has 2 loops in it") {
REQUIRE(print.skirt().items_count() == 2);
REQUIRE(print.skirt().flatten().entities.size() == 2);
}
}
}
}
SCENARIO("Print: Changing number of solid surfaces does not cause all surfaces to become internal.", "[Print]") {
GIVEN("sliced 20mm cube and config with top_solid_surfaces = 2 and bottom_solid_surfaces = 1") {
Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
config.set_deserialize({
{ "top_solid_layers", 2 },
{ "bottom_solid_layers", 1 },
{ "layer_height", 0.5 }, // get a known number of layers
{ "first_layer_height", 0.5 }
});
Slic3r::Print print;
Slic3r::Model model;
Slic3r::Test::init_print({TestMesh::cube_20x20x20}, print, model, config);
// Precondition: Ensure that the model has 2 solid top layers (39, 38)
// and one solid bottom layer (0).
auto test_is_solid_infill = [&print](size_t obj_id, size_t layer_id) {
const Layer &layer = *(print.objects().at(obj_id)->get_layer((int)layer_id));
// iterate over all of the regions in the layer
for (const LayerRegion *region : layer.regions()) {
// for each region, iterate over the fill surfaces
for (const Surface &surface : region->fill_surfaces.surfaces)
CHECK(surface.is_solid());
}
};
print.process();
test_is_solid_infill(0, 0); // should be solid
test_is_solid_infill(0, 39); // should be solid
test_is_solid_infill(0, 38); // should be solid
WHEN("Model is re-sliced with top_solid_layers == 3") {
config.set("top_solid_layers", 3);
print.apply(model, config);
print.process();
THEN("Print object does not have 0 solid bottom layers.") {
test_is_solid_infill(0, 0);
}
AND_THEN("Print object has 3 top solid layers") {
test_is_solid_infill(0, 39);
test_is_solid_infill(0, 38);
test_is_solid_infill(0, 37);
}
}
}
}
SCENARIO("Print: Brim generation", "[Print]") {
GIVEN("20mm cube and default config, 1mm first layer width") {
WHEN("Brim is set to 3mm") {
Slic3r::Print print;
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, {
{ "first_layer_extrusion_width", 1 },
{ "brim_width", 3 }
});
THEN("Brim Extrusion collection has 3 loops in it") {
REQUIRE(print.brim().items_count() == 3);
}
}
WHEN("Brim is set to 6mm") {
Slic3r::Print print;
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, {
{ "first_layer_extrusion_width", 1 },
{ "brim_width", 6 }
});
THEN("Brim Extrusion collection has 6 loops in it") {
REQUIRE(print.brim().items_count() == 6);
}
}
WHEN("Brim is set to 6mm, extrusion width 0.5mm") {
Slic3r::Print print;
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, {
{ "first_layer_extrusion_width", 1 },
{ "brim_width", 6 },
{ "first_layer_extrusion_width", 0.5 }
});
print.process();
THEN("Brim Extrusion collection has 12 loops in it") {
REQUIRE(print.brim().items_count() == 14);
}
}
}
}

View File

@ -0,0 +1,270 @@
#include <catch2/catch.hpp>
#include "libslic3r/libslic3r.h"
#include "libslic3r/GCodeReader.hpp"
#include "test_data.hpp"
#include <algorithm>
#include <regex>
using namespace Slic3r;
using namespace Slic3r::Test;
std::regex perimeters_regex("G1 X[-0-9.]* Y[-0-9.]* E[-0-9.]* ; perimeter");
std::regex infill_regex("G1 X[-0-9.]* Y[-0-9.]* E[-0-9.]* ; infill");
std::regex skirt_regex("G1 X[-0-9.]* Y[-0-9.]* E[-0-9.]* ; skirt");
SCENARIO( "PrintGCode basic functionality", "[PrintGCode]") {
GIVEN("A default configuration and a print test object") {
WHEN("the output is executed with no support material") {
Slic3r::Print print;
Slic3r::Model model;
Slic3r::Test::init_print({TestMesh::cube_20x20x20}, print, model, {
{ "layer_height", 0.2 },
{ "first_layer_height", 0.2 },
{ "first_layer_extrusion_width", 0 },
{ "gcode_comments", true },
{ "start_gcode", "" }
});
std::string gcode = Slic3r::Test::gcode(print);
THEN("Some text output is generated.") {
REQUIRE(gcode.size() > 0);
}
THEN("Exported text contains slic3r version") {
REQUIRE(gcode.find(SLIC3R_VERSION) != std::string::npos);
}
//THEN("Exported text contains git commit id") {
// REQUIRE(gcode.find("; Git Commit") != std::string::npos);
// REQUIRE(gcode.find(SLIC3R_BUILD_ID) != std::string::npos);
//}
THEN("Exported text contains extrusion statistics.") {
REQUIRE(gcode.find("; external perimeters extrusion width") != std::string::npos);
REQUIRE(gcode.find("; perimeters extrusion width") != std::string::npos);
REQUIRE(gcode.find("; infill extrusion width") != std::string::npos);
REQUIRE(gcode.find("; solid infill extrusion width") != std::string::npos);
REQUIRE(gcode.find("; top infill extrusion width") != std::string::npos);
REQUIRE(gcode.find("; support material extrusion width") == std::string::npos);
REQUIRE(gcode.find("; first layer extrusion width") == std::string::npos);
}
THEN("Exported text does not contain cooling markers (they were consumed)") {
REQUIRE(gcode.find(";_EXTRUDE_SET_SPEED") == std::string::npos);
}
THEN("GCode preamble is emitted.") {
REQUIRE(gcode.find("G21 ; set units to millimeters") != std::string::npos);
}
THEN("Config options emitted for print config, default region config, default object config") {
REQUIRE(gcode.find("; first_layer_temperature") != std::string::npos);
REQUIRE(gcode.find("; layer_height") != std::string::npos);
REQUIRE(gcode.find("; fill_density") != std::string::npos);
}
THEN("Infill is emitted.") {
std::smatch has_match;
REQUIRE(std::regex_search(gcode, has_match, infill_regex));
}
THEN("Perimeters are emitted.") {
std::smatch has_match;
REQUIRE(std::regex_search(gcode, has_match, perimeters_regex));
}
THEN("Skirt is emitted.") {
std::smatch has_match;
REQUIRE(std::regex_search(gcode, has_match, skirt_regex));
}
THEN("final Z height is 20mm") {
double final_z = 0.0;
GCodeReader reader;
reader.apply_config(print.config());
reader.parse_buffer(gcode, [&final_z] (GCodeReader& self, const GCodeReader::GCodeLine& line) {
final_z = std::max<double>(final_z, static_cast<double>(self.z())); // record the highest Z point we reach
});
REQUIRE(final_z == Approx(20.));
}
}
WHEN("output is executed with complete objects and two differently-sized meshes") {
Slic3r::Print print;
Slic3r::Model model;
Slic3r::Test::init_print({TestMesh::cube_20x20x20,TestMesh::cube_20x20x20}, print, model, {
{ "first_layer_extrusion_width", 0 },
{ "first_layer_height", 0.3 },
{ "layer_height", 0.2 },
{ "support_material", false },
{ "raft_layers", 0 },
{ "complete_objects", true },
{ "gcode_comments", true },
{ "between_objects_gcode", "; between-object-gcode" }
});
std::string gcode = Slic3r::Test::gcode(print);
THEN("Some text output is generated.") {
REQUIRE(gcode.size() > 0);
}
THEN("Infill is emitted.") {
std::smatch has_match;
REQUIRE(std::regex_search(gcode, has_match, infill_regex));
}
THEN("Perimeters are emitted.") {
std::smatch has_match;
REQUIRE(std::regex_search(gcode, has_match, perimeters_regex));
}
THEN("Skirt is emitted.") {
std::smatch has_match;
REQUIRE(std::regex_search(gcode, has_match, skirt_regex));
}
THEN("Between-object-gcode is emitted.") {
REQUIRE(gcode.find("; between-object-gcode") != std::string::npos);
}
THEN("final Z height is 20.1mm") {
double final_z = 0.0;
GCodeReader reader;
reader.apply_config(print.config());
reader.parse_buffer(gcode, [&final_z] (GCodeReader& self, const GCodeReader::GCodeLine& line) {
final_z = std::max(final_z, static_cast<double>(self.z())); // record the highest Z point we reach
});
REQUIRE(final_z == Approx(20.1));
}
THEN("Z height resets on object change") {
double final_z = 0.0;
bool reset = false;
GCodeReader reader;
reader.apply_config(print.config());
reader.parse_buffer(gcode, [&final_z, &reset] (GCodeReader& self, const GCodeReader::GCodeLine& line) {
if (final_z > 0 && std::abs(self.z() - 0.3) < 0.01 ) { // saw higher Z before this, now it's lower
reset = true;
} else {
final_z = std::max(final_z, static_cast<double>(self.z())); // record the highest Z point we reach
}
});
REQUIRE(reset == true);
}
THEN("Shorter object is printed before taller object.") {
double final_z = 0.0;
bool reset = false;
GCodeReader reader;
reader.apply_config(print.config());
reader.parse_buffer(gcode, [&final_z, &reset] (GCodeReader& self, const GCodeReader::GCodeLine& line) {
if (final_z > 0 && std::abs(self.z() - 0.3) < 0.01 ) {
reset = (final_z > 20.0);
} else {
final_z = std::max(final_z, static_cast<double>(self.z())); // record the highest Z point we reach
}
});
REQUIRE(reset == true);
}
}
WHEN("the output is executed with support material") {
std::string gcode = ::Test::slice({TestMesh::cube_20x20x20}, {
{ "first_layer_extrusion_width", 0 },
{ "support_material", true },
{ "raft_layers", 3 },
{ "gcode_comments", true }
});
THEN("Some text output is generated.") {
REQUIRE(gcode.size() > 0);
}
THEN("Exported text contains extrusion statistics.") {
REQUIRE(gcode.find("; external perimeters extrusion width") != std::string::npos);
REQUIRE(gcode.find("; perimeters extrusion width") != std::string::npos);
REQUIRE(gcode.find("; infill extrusion width") != std::string::npos);
REQUIRE(gcode.find("; solid infill extrusion width") != std::string::npos);
REQUIRE(gcode.find("; top infill extrusion width") != std::string::npos);
REQUIRE(gcode.find("; support material extrusion width") != std::string::npos);
REQUIRE(gcode.find("; first layer extrusion width") == std::string::npos);
}
THEN("Raft is emitted.") {
REQUIRE(gcode.find("; raft") != std::string::npos);
}
}
WHEN("the output is executed with a separate first layer extrusion width") {
std::string gcode = ::Test::slice({ TestMesh::cube_20x20x20 }, {
{ "first_layer_extrusion_width", "0.5" }
});
THEN("Some text output is generated.") {
REQUIRE(gcode.size() > 0);
}
THEN("Exported text contains extrusion statistics.") {
REQUIRE(gcode.find("; external perimeters extrusion width") != std::string::npos);
REQUIRE(gcode.find("; perimeters extrusion width") != std::string::npos);
REQUIRE(gcode.find("; infill extrusion width") != std::string::npos);
REQUIRE(gcode.find("; solid infill extrusion width") != std::string::npos);
REQUIRE(gcode.find("; top infill extrusion width") != std::string::npos);
REQUIRE(gcode.find("; support material extrusion width") == std::string::npos);
REQUIRE(gcode.find("; first layer extrusion width") != std::string::npos);
}
}
WHEN("Cooling is enabled and the fan is disabled.") {
std::string gcode = ::Test::slice({ TestMesh::cube_20x20x20 }, {
{ "cooling", true },
{ "disable_fan_first_layers", 5 }
});
THEN("GCode to disable fan is emitted."){
REQUIRE(gcode.find("M107") != std::string::npos);
}
}
WHEN("end_gcode exists with layer_num and layer_z") {
std::string gcode = ::Test::slice({ TestMesh::cube_20x20x20 }, {
{ "end_gcode", "; Layer_num [layer_num]\n; Layer_z [layer_z]" },
{ "layer_height", 0.1 },
{ "first_layer_height", 0.1 }
});
THEN("layer_num and layer_z are processed in the end gcode") {
REQUIRE(gcode.find("; Layer_num 199") != std::string::npos);
REQUIRE(gcode.find("; Layer_z 20") != std::string::npos);
}
}
WHEN("current_extruder exists in start_gcode") {
{
std::string gcode = ::Test::slice({ TestMesh::cube_20x20x20 }, {
{ "start_gcode", "; Extruder [current_extruder]" }
});
THEN("current_extruder is processed in the start gcode and set for first extruder") {
REQUIRE(gcode.find("; Extruder 0") != std::string::npos);
}
}
{
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
config.set_num_extruders(4);
config.set_deserialize({
{ "start_gcode", "; Extruder [current_extruder]" },
{ "infill_extruder", 2 },
{ "solid_infill_extruder", 2 },
{ "perimeter_extruder", 2 },
{ "support_material_extruder", 2 },
{ "support_material_interface_extruder", 2 }
});
std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config);
THEN("current_extruder is processed in the start gcode and set for second extruder") {
REQUIRE(gcode.find("; Extruder 1") != std::string::npos);
}
}
}
WHEN("layer_num represents the layer's index from z=0") {
std::string gcode = ::Test::slice({ TestMesh::cube_20x20x20, TestMesh::cube_20x20x20 }, {
{ "complete_objects", true },
{ "gcode_comments", true },
{ "layer_gcode", ";Layer:[layer_num] ([layer_z] mm)" },
{ "layer_height", 1.0 },
{ "first_layer_height", 1.0 }
});
// End of the 1st object.
size_t pos = gcode.find(";Layer:19 ");
THEN("First and second object last layer is emitted") {
// First object
REQUIRE(pos != std::string::npos);
pos += 10;
REQUIRE(pos < gcode.size());
double z = 0;
REQUIRE((sscanf(gcode.data() + pos, "(%lf mm)", &z) == 1));
REQUIRE(z == Approx(20.));
// Second object
pos = gcode.find(";Layer:39 ", pos);
REQUIRE(pos != std::string::npos);
pos += 10;
REQUIRE(pos < gcode.size());
REQUIRE((sscanf(gcode.data() + pos, "(%lf mm)", &z) == 1));
REQUIRE(z == Approx(20.));
}
}
}
}

View File

@ -0,0 +1,88 @@
#include <catch2/catch.hpp>
#include "libslic3r/libslic3r.h"
#include "libslic3r/Print.hpp"
#include "test_data.hpp"
using namespace Slic3r;
using namespace Slic3r::Test;
SCENARIO("PrintObject: object layer heights", "[PrintObject]") {
GIVEN("20mm cube and default initial config, initial layer height of 2mm") {
WHEN("generate_object_layers() is called for 2mm layer heights and nozzle diameter of 3mm") {
Slic3r::Print print;
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, {
{ "first_layer_height", 2 },
{ "layer_height", 2 },
{ "nozzle_diameter", 3 }
});
const std::vector<Slic3r::Layer*> &layers = print.objects().front()->layers();
THEN("The output vector has 10 entries") {
REQUIRE(layers.size() == 10);
}
AND_THEN("Each layer is approximately 2mm above the previous Z") {
coordf_t last = 0.0;
for (size_t i = 0; i < layers.size(); ++ i) {
REQUIRE((layers[i]->print_z - last) == Approx(2.0));
last = layers[i]->print_z;
}
}
}
WHEN("generate_object_layers() is called for 10mm layer heights and nozzle diameter of 11mm") {
Slic3r::Print print;
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, {
{ "first_layer_height", 2 },
{ "layer_height", 10 },
{ "nozzle_diameter", 11 }
});
const std::vector<Slic3r::Layer*> &layers = print.objects().front()->layers();
THEN("The output vector has 3 entries") {
REQUIRE(layers.size() == 3);
}
AND_THEN("Layer 0 is at 2mm") {
REQUIRE(layers.front()->print_z == Approx(2.0));
}
AND_THEN("Layer 1 is at 12mm") {
REQUIRE(layers[1]->print_z == Approx(12.0));
}
}
WHEN("generate_object_layers() is called for 15mm layer heights and nozzle diameter of 16mm") {
Slic3r::Print print;
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, {
{ "first_layer_height", 2 },
{ "layer_height", 15 },
{ "nozzle_diameter", 16 }
});
const std::vector<Slic3r::Layer*> &layers = print.objects().front()->layers();
THEN("The output vector has 2 entries") {
REQUIRE(layers.size() == 2);
}
AND_THEN("Layer 0 is at 2mm") {
REQUIRE(layers[0]->print_z == Approx(2.0));
}
AND_THEN("Layer 1 is at 17mm") {
REQUIRE(layers[1]->print_z == Approx(17.0));
}
}
#if 0
WHEN("generate_object_layers() is called for 15mm layer heights and nozzle diameter of 5mm") {
Slic3r::Print print;
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, {
{ "first_layer_height", 2 },
{ "layer_height", 15 },
{ "nozzle_diameter", 5 }
});
const std::vector<Slic3r::Layer*> &layers = print.objects().front()->layers();
THEN("The layer height is limited to 5mm.") {
CHECK(layers.size() == 5);
coordf_t last = 2.0;
for (size_t i = 1; i < layers.size(); i++) {
REQUIRE((layers[i]->print_z - last) == Approx(5.0));
last = layers[i]->print_z;
}
}
}
#endif
}
}

View File

@ -12,251 +12,241 @@ using namespace Slic3r::Test;
using namespace Slic3r;
/// Helper method to find the tool used for the brim (always the first extrusion)
int get_brim_tool(std::string &gcode, Slic3r::GCodeReader& parser) {
static int get_brim_tool(const std::string &gcode)
{
int brim_tool = -1;
int tool = -1;
parser.parse_buffer(gcode, [&tool, &brim_tool] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line)
{
// if the command is a T command, set the the current tool
if (boost::starts_with(line.cmd(), "T")) {
tool = atoi(line.cmd().data() + 1);
} else if (line.cmd() == "G1" && line.extruding(self) && line.dist_XY(self) > 0 && brim_tool < 0) {
brim_tool = tool;
}
});
GCodeReader parser;
parser.parse_buffer(gcode, [&tool, &brim_tool] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line)
{
// if the command is a T command, set the the current tool
if (boost::starts_with(line.cmd(), "T")) {
tool = atoi(line.cmd().data() + 1);
} else if (line.cmd() == "G1" && line.extruding(self) && line.dist_XY(self) > 0 && brim_tool < 0) {
brim_tool = tool;
}
});
return brim_tool;
}
TEST_CASE("Skirt height is honored") {
std::shared_ptr<Slic3r::DynamicPrintConfig> config(Slic3r::DynamicPrintConfig::new_from_defaults());
config->opt_int("skirts") = 1;
config->opt_int("skirt_height") = 5;
config->opt_int("perimeters") = 0;
config->opt_float("support_material_speed") = 99;
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
config.set_deserialize({
{ "skirts", 1 },
{ "skirt_height", 5 },
{ "perimeters", 0 },
{ "support_material_speed", 99 },
// avoid altering speeds unexpectedly
{ "cooling", false },
{ "first_layer_speed", "100%" }
});
// avoid altering speeds unexpectedly
config->set_deserialize("cooling", "0");
config->set_deserialize("first_layer_speed", "100%");
double support_speed = config->opt<Slic3r::ConfigOptionFloat>("support_material_speed")->value * MM_PER_MIN;
std::string gcode;
SECTION("printing a single object") {
gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config);
}
SECTION("printing multiple objects") {
gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20, TestMesh::cube_20x20x20}, config);
}
std::map<double, bool> layers_with_skirt;
std::string gcode;
double support_speed = config.opt<Slic3r::ConfigOptionFloat>("support_material_speed")->value * MM_PER_MIN;
GCodeReader parser;
Slic3r::Model model;
SECTION("printing a single object") {
auto print = Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config);
gcode = Slic3r::Test::gcode(print);
}
SECTION("printing multiple objects") {
auto print = Slic3r::Test::init_print({TestMesh::cube_20x20x20, TestMesh::cube_20x20x20}, model, config);
gcode = Slic3r::Test::gcode(print);
}
parser.parse_buffer(gcode, [&layers_with_skirt, &support_speed] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line)
{
parser.parse_buffer(gcode, [&layers_with_skirt, &support_speed] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
if (line.extruding(self) && self.f() == Approx(support_speed)) {
layers_with_skirt[self.z()] = 1;
}
});
REQUIRE(layers_with_skirt.size() == (size_t)config->opt_int("skirt_height"));
REQUIRE(layers_with_skirt.size() == (size_t)config.opt_int("skirt_height"));
}
SCENARIO("Original Slic3r Skirt/Brim tests", "[!mayfail]") {
Slic3r::GCodeReader parser;
Slic3r::Model model;
std::string gcode;
GIVEN("A default configuration") {
std::shared_ptr<Slic3r::DynamicPrintConfig> config(Slic3r::DynamicPrintConfig::new_from_defaults());
config->set_num_extruders(4);
config->opt_float("support_material_speed") = 99;
config->set_deserialize("first_layer_height", "0.3");
config->set_deserialize("gcode_comments", "1");
// avoid altering speeds unexpectedly
config->set_deserialize("cooling", "0");
config->set_deserialize("first_layer_speed", "100%");
// remove noise from top/solid layers
config->opt_int("top_solid_layers") = 0;
config->opt_int("bottom_solid_layers") = 1;
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
config.set_num_extruders(4);
config.set_deserialize({
{ "support_material_speed", 99 },
{ "first_layer_height", 0.3 },
{ "gcode_comments", true },
// avoid altering speeds unexpectedly
{ "cooling", false },
{ "first_layer_speed", "100%" },
// remove noise from top/solid layers
{ "top_solid_layers", 0 },
{ "bottom_solid_layers", 1 }
});
WHEN("Brim width is set to 5") {
config->opt_int("perimeters") = 0;
config->opt_int("skirts") = 0;
config->opt_float("brim_width") = 5;
THEN("Brim is generated") {
auto print = Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config);
gcode = Slic3r::Test::gcode(print);
config.set_deserialize({
{ "perimeters", 0 },
{ "skirts", 0 },
{ "brim_width", 5 }
});
THEN("Brim is generated") {
std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config);
bool brim_generated = false;
double support_speed = config->opt<Slic3r::ConfigOptionFloat>("support_material_speed")->value * MM_PER_MIN;
parser.parse_buffer(gcode, [&brim_generated, support_speed] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line)
{
if (self.z() == Approx(0.3) || line.new_Z(self) == Approx(0.3)) {
if (line.extruding(self) && self.f() == Approx(support_speed)) {
brim_generated = true;
}
double support_speed = config.opt<Slic3r::ConfigOptionFloat>("support_material_speed")->value * MM_PER_MIN;
Slic3r::GCodeReader parser;
parser.parse_buffer(gcode, [&brim_generated, support_speed] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line) {
if (self.z() == Approx(0.3) || line.new_Z(self) == Approx(0.3)) {
if (line.extruding(self) && self.f() == Approx(support_speed)) {
brim_generated = true;
}
});
}
});
REQUIRE(brim_generated);
}
}
WHEN("Skirt area is smaller than the brim") {
config->opt_int("skirts") = 1;
config->opt_float("brim_width") = 10;
auto print = Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config);
config.set_deserialize({
{ "skirts", 1 },
{ "brim_width", 10}
});
THEN("Gcode generates") {
REQUIRE(! Slic3r::Test::gcode(print).empty());
REQUIRE(! Slic3r::Test::slice({TestMesh::cube_20x20x20}, config).empty());
}
}
WHEN("Skirt height is 0 and skirts > 0") {
config->opt_int("skirts") = 2;
config->opt_int("skirt_height") = 0;
auto print = Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config);
config.set_deserialize({
{ "skirts", 2 },
{ "skirt_height", 0 }
});
THEN("Gcode generates") {
REQUIRE(! Slic3r::Test::gcode(print).empty());
REQUIRE(! Slic3r::Test::slice({TestMesh::cube_20x20x20}, config).empty());
}
}
WHEN("Perimeter extruder = 2 and support extruders = 3") {
config->opt_int("skirts") = 0;
config->opt_float("brim_width") = 5;
config->opt_int("perimeter_extruder") = 2;
config->opt_int("support_material_extruder") = 3;
THEN("Brim is printed with the extruder used for the perimeters of first object") {
auto print = Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config);
gcode = Slic3r::Test::gcode(print);
int tool = get_brim_tool(gcode, parser);
REQUIRE(tool == config->opt_int("perimeter_extruder") - 1);
std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, {
{ "skirts", 0 },
{ "brim_width", 5 },
{ "perimeter_extruder", 2 },
{ "support_material_extruder", 3 }
});
int tool = get_brim_tool(gcode);
REQUIRE(tool == config.opt_int("perimeter_extruder") - 1);
}
}
WHEN("Perimeter extruder = 2, support extruders = 3, raft is enabled") {
config->opt_int("skirts") = 0;
config->opt_float("brim_width") = 5;
config->opt_int("perimeter_extruder") = 2;
config->opt_int("support_material_extruder") = 3;
config->opt_int("raft_layers") = 1;
THEN("brim is printed with same extruder as skirt") {
auto print = Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config);
gcode = Slic3r::Test::gcode(print);
int tool = get_brim_tool(gcode, parser);
REQUIRE(tool == config->opt_int("support_material_extruder") - 1);
std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, {
{ "skirts", 0 },
{ "brim_width", 5 },
{ "perimeter_extruder", 2 },
{ "support_material_extruder", 3 },
{ "raft_layers", 1 }
});
int tool = get_brim_tool(gcode);
REQUIRE(tool == config.opt_int("support_material_extruder") - 1);
}
}
WHEN("brim width to 1 with layer_width of 0.5") {
config->opt_int("skirts") = 0;
config->set_deserialize("first_layer_extrusion_width", "0.5");
config->opt_float("brim_width") = 1;
config.set_deserialize({
{ "skirts", 0 },
{ "first_layer_extrusion_width", 0.5 },
{ "brim_width", 1 }
});
THEN("2 brim lines") {
Slic3r::Model model;
auto print = Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config);
print->process();
REQUIRE(print->brim().entities.size() == 2);
Slic3r::Print print;
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, config);
REQUIRE(print.brim().entities.size() == 2);
}
}
#if 0
WHEN("brim ears on a square") {
config->opt_int("skirts") = 0);
config->set_deserialize("first_layer_extrusion_width", "0.5");
config->opt_float("brim_width") = 1;
config->set("brim_ears", true);
config->set("brim_ears_max_angle", 91);
Slic3r::Model model;
auto print = Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config);
print->process();
config.set_deserialize({
{ "skirts", 0 },
{ "first_layer_extrusion_width", 0.5 },
{ "brim_width", 1 },
{ "brim_ears", 1 },
{ "brim_ears_max_angle", 91 }
});
Slic3r::Print print;
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, config);
THEN("Four brim ears") {
REQUIRE(print->brim.size() == 4);
REQUIRE(print.brim().entities.size() == 4);
}
}
WHEN("brim ears on a square but with a too small max angle") {
config->set("skirts", 0);
config->set("first_layer_extrusion_width", 0.5);
config->set("brim_width", 1);
config->set("brim_ears", true);
config->set("brim_ears_max_angle", 89);
config.set_deserialize({
{ "skirts", 0 },
{ "first_layer_extrusion_width", 0.5 },
{ "brim_width", 1 },
{ "brim_ears", 1 },
{ "brim_ears_max_angle", 89 }
});
THEN("no brim") {
Slic3r::Model model;
auto print = Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config);
print->process();
REQUIRE(print->brim.size() == 0);
Slic3r::Print print;
Slic3r::Test::init_and_process_print({ TestMesh::cube_20x20x20 }, print, config);
REQUIRE(print.brim().entities.size() == 0);
}
}
#endif
WHEN("Object is plated with overhang support and a brim") {
config->opt_float("layer_height") = 0.4;
config->set_deserialize("first_layer_height", "0.4");
config->opt_int("skirts") = 1;
config->opt_float("skirt_distance") = 0;
config->opt_float("support_material_speed") = 99;
config->opt_int("perimeter_extruder") = 1;
config->opt_int("support_material_extruder") = 2;
config->opt_int("infill_extruder") = 3; // ensure that a tool command gets emitted.
config->set_deserialize("cooling", "0"); // to prevent speeds to be altered
config->set_deserialize("first_layer_speed", "100%"); // to prevent speeds to be altered
config.set_deserialize({
{ "layer_height", 0.4 },
{ "first_layer_height", 0.4 },
{ "skirts", 1 },
{ "skirt_distance", 0 },
{ "support_material_speed", 99 },
{ "perimeter_extruder", 1 },
{ "support_material_extruder", 2 },
{ "infill_extruder", 3 }, // ensure that a tool command gets emitted.
{ "cooling", false }, // to prevent speeds to be altered
{ "first_layer_speed", "100%" }, // to prevent speeds to be altered
});
Slic3r::Model model;
auto print = Slic3r::Test::init_print({TestMesh::overhang}, model, config);
print->process();
THEN("overhang generates?") {
//FIXME does it make sense?
REQUIRE(! Slic3r::Test::slice({TestMesh::overhang}, config).empty());
}
// config->set("support_material", true); // to prevent speeds to be altered
// config.set("support_material", true); // to prevent speeds to be altered
THEN("skirt length is large enough to contain object with support") {
CHECK(config->opt_bool("support_material")); // test is not valid if support material is off
double skirt_length = 0.0;
Points extrusion_points;
int tool = -1;
auto print = Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config);
std::string gcode = Slic3r::Test::gcode(print);
double support_speed = config->opt<ConfigOptionFloat>("support_material_speed")->value * MM_PER_MIN;
parser.parse_buffer(gcode, [config, &extrusion_points, &tool, &skirt_length, support_speed] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line)
{
// std::cerr << line.cmd() << "\n";
if (boost::starts_with(line.cmd(), "T")) {
tool = atoi(line.cmd().data() + 1);
} else if (self.z() == Approx(config->opt<ConfigOptionFloat>("first_layer_height")->value)) {
// on first layer
if (line.extruding(self) && line.dist_XY(self) > 0) {
float speed = ( self.f() > 0 ? self.f() : line.new_F(self));
// std::cerr << "Tool " << tool << "\n";
if (speed == Approx(support_speed) && tool == config->opt_int("perimeter_extruder") - 1) {
// Skirt uses first material extruder, support material speed.
skirt_length += line.dist_XY(self);
} else {
extrusion_points.push_back(Slic3r::Point::new_scale(line.new_X(self), line.new_Y(self)));
}
}
CHECK(config.opt_bool("support_material")); // test is not valid if support material is off
std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config);
double support_speed = config.opt<ConfigOptionFloat>("support_material_speed")->value * MM_PER_MIN;
double skirt_length = 0.0;
Points extrusion_points;
int tool = -1;
GCodeReader parser;
parser.parse_buffer(gcode, [config, &extrusion_points, &tool, &skirt_length, support_speed] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line) {
// std::cerr << line.cmd() << "\n";
if (boost::starts_with(line.cmd(), "T")) {
tool = atoi(line.cmd().data() + 1);
} else if (self.z() == Approx(config.opt<ConfigOptionFloat>("first_layer_height")->value)) {
// on first layer
if (line.extruding(self) && line.dist_XY(self) > 0) {
float speed = ( self.f() > 0 ? self.f() : line.new_F(self));
// std::cerr << "Tool " << tool << "\n";
if (speed == Approx(support_speed) && tool == config.opt_int("perimeter_extruder") - 1) {
// Skirt uses first material extruder, support material speed.
skirt_length += line.dist_XY(self);
} else
extrusion_points.push_back(Slic3r::Point::new_scale(line.new_X(self), line.new_Y(self)));
}
if (self.z() == Approx(0.3) || line.new_Z(self) == Approx(0.3)) {
if (line.extruding(self) && self.f() == Approx(support_speed)) {
}
}
if (self.z() == Approx(0.3) || line.new_Z(self) == Approx(0.3)) {
if (line.extruding(self) && self.f() == Approx(support_speed)) {
}
});
}
});
Slic3r::Polygon convex_hull = Slic3r::Geometry::convex_hull(extrusion_points);
double hull_perimeter = unscale<double>(convex_hull.split_at_first_point().length());
REQUIRE(skirt_length > hull_perimeter);
}
}
WHEN("Large minimum skirt length is used.") {
config->opt_float("min_skirt_length") = 20;
Slic3r::Model model;
auto print = Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config);
config.set("min_skirt_length", 20);
THEN("Gcode generation doesn't crash") {
REQUIRE(! Slic3r::Test::gcode(print).empty());
REQUIRE(! Slic3r::Test::slice({TestMesh::cube_20x20x20}, config).empty());
}
}
}

View File

@ -0,0 +1,233 @@
#include <catch2/catch.hpp>
#include "libslic3r/GCodeReader.hpp"
#include "test_data.hpp" // get access to init_print, etc
using namespace Slic3r::Test;
using namespace Slic3r;
TEST_CASE("SupportMaterial: Three raft layers created", "[SupportMaterial]")
{
Slic3r::Print print;
Slic3r::Test::init_and_process_print({ TestMesh::cube_20x20x20 }, print, {
{ "support_material", 1 },
{ "raft_layers", 3 }
});
REQUIRE(print.objects().front()->support_layers().size() == 3);
}
SCENARIO("SupportMaterial: support_layers_z and contact_distance", "[SupportMaterial]")
{
// Box h = 20mm, hole bottom at 5mm, hole height 10mm (top edge at 15mm).
TriangleMesh mesh = Slic3r::Test::mesh(Slic3r::Test::TestMesh::cube_with_hole);
mesh.rotate_x(float(M_PI / 2));
auto check = [](Slic3r::Print &print, bool &first_support_layer_height_ok, bool &layer_height_minimum_ok, bool &layer_height_maximum_ok, bool &top_spacing_ok)
{
const std::vector<Slic3r::SupportLayer*> &support_layers = print.objects().front()->support_layers();
first_support_layer_height_ok = support_layers.front()->print_z == print.default_object_config().first_layer_height.value;
layer_height_minimum_ok = true;
layer_height_maximum_ok = true;
double min_layer_height = print.config().min_layer_height.values.front();
double max_layer_height = print.config().nozzle_diameter.values.front();
if (print.config().max_layer_height.values.front() > EPSILON)
max_layer_height = std::min(max_layer_height, print.config().max_layer_height.values.front());
for (size_t i = 1; i < support_layers.size(); ++ i) {
if (support_layers[i]->print_z - support_layers[i - 1]->print_z < min_layer_height - EPSILON)
layer_height_minimum_ok = false;
if (support_layers[i]->print_z - support_layers[i - 1]->print_z > max_layer_height + EPSILON)
layer_height_maximum_ok = false;
}
#if 0
double expected_top_spacing = print.default_object_config().layer_height + print.config().nozzle_diameter.get_at(0);
bool wrong_top_spacing = 0;
std::vector<coordf_t> top_z { 1.1 };
for (coordf_t top_z_el : top_z) {
// find layer index of this top surface.
size_t layer_id = -1;
for (size_t i = 0; i < support_z.size(); ++ i) {
if (abs(support_z[i] - top_z_el) < EPSILON) {
layer_id = i;
i = static_cast<int>(support_z.size());
}
}
// check that first support layer above this top surface (or the next one) is spaced with nozzle diameter
if (abs(support_z[layer_id + 1] - support_z[layer_id] - expected_top_spacing) > EPSILON &&
abs(support_z[layer_id + 2] - support_z[layer_id] - expected_top_spacing) > EPSILON) {
wrong_top_spacing = 1;
}
}
d = ! wrong_top_spacing;
#else
top_spacing_ok = true;
#endif
};
GIVEN("A print object having one modelObject") {
WHEN("First layer height = 0.4") {
Slic3r::Print print;
Slic3r::Test::init_and_process_print({ mesh }, print, {
{ "support_material", 1 },
{ "layer_height", 0.2 },
{ "first_layer_height", 0.4 },
});
bool a, b, c, d;
check(print, a, b, c, d);
THEN("First layer height is honored") { REQUIRE(a == true); }
THEN("No null or negative support layers") { REQUIRE(b == true); }
THEN("No layers thicker than nozzle diameter") { REQUIRE(c == true); }
// THEN("Layers above top surfaces are spaced correctly") { REQUIRE(d == true); }
}
WHEN("Layer height = 0.2 and, first layer height = 0.3") {
Slic3r::Print print;
Slic3r::Test::init_and_process_print({ mesh }, print, {
{ "support_material", 1 },
{ "layer_height", 0.2 },
{ "first_layer_height", 0.3 },
});
bool a, b, c, d;
check(print, a, b, c, d);
THEN("First layer height is honored") { REQUIRE(a == true); }
THEN("No null or negative support layers") { REQUIRE(b == true); }
THEN("No layers thicker than nozzle diameter") { REQUIRE(c == true); }
// THEN("Layers above top surfaces are spaced correctly") { REQUIRE(d == true); }
}
WHEN("Layer height = nozzle_diameter[0]") {
Slic3r::Print print;
Slic3r::Test::init_and_process_print({ mesh }, print, {
{ "support_material", 1 },
{ "layer_height", 0.2 },
{ "first_layer_height", 0.3 },
});
bool a, b, c, d;
check(print, a, b, c, d);
THEN("First layer height is honored") { REQUIRE(a == true); }
THEN("No null or negative support layers") { REQUIRE(b == true); }
THEN("No layers thicker than nozzle diameter") { REQUIRE(c == true); }
// THEN("Layers above top surfaces are spaced correctly") { REQUIRE(d == true); }
}
}
}
#if 0
// Test 8.
TEST_CASE("SupportMaterial: forced support is generated", "[SupportMaterial]")
{
// Create a mesh & modelObject.
TriangleMesh mesh = TriangleMesh::make_cube(20, 20, 20);
Model model = Model();
ModelObject *object = model.add_object();
object->add_volume(mesh);
model.add_default_instances();
model.align_instances_to_origin();
Print print = Print();
std::vector<coordf_t> contact_z = {1.9};
std::vector<coordf_t> top_z = {1.1};
print.default_object_config.support_material_enforce_layers = 100;
print.default_object_config.support_material = 0;
print.default_object_config.layer_height = 0.2;
print.default_object_config.set_deserialize("first_layer_height", "0.3");
print.add_model_object(model.objects[0]);
print.objects.front()->_slice();
SupportMaterial *support = print.objects.front()->_support_material();
auto support_z = support->support_layers_z(contact_z, top_z, print.default_object_config.layer_height);
bool check = true;
for (size_t i = 1; i < support_z.size(); i++) {
if (support_z[i] - support_z[i - 1] <= 0)
check = false;
}
REQUIRE(check == true);
}
// TODO
bool test_6_checks(Print& print)
{
bool has_bridge_speed = true;
// Pre-Processing.
PrintObject* print_object = print.objects.front();
print_object->infill();
SupportMaterial* support_material = print.objects.front()->_support_material();
support_material->generate(print_object);
// TODO but not needed in test 6 (make brims and make skirts).
// Exporting gcode.
// TODO validation found in Simple.pm
return has_bridge_speed;
}
// Test 6.
SCENARIO("SupportMaterial: Checking bridge speed", "[SupportMaterial]")
{
GIVEN("Print object") {
// Create a mesh & modelObject.
TriangleMesh mesh = TriangleMesh::make_cube(20, 20, 20);
Model model = Model();
ModelObject *object = model.add_object();
object->add_volume(mesh);
model.add_default_instances();
model.align_instances_to_origin();
Print print = Print();
print.config.brim_width = 0;
print.config.skirts = 0;
print.config.skirts = 0;
print.default_object_config.support_material = 1;
print.default_region_config.top_solid_layers = 0; // so that we don't have the internal bridge over infill.
print.default_region_config.bridge_speed = 99;
print.config.cooling = 0;
print.config.set_deserialize("first_layer_speed", "100%");
WHEN("support_material_contact_distance = 0.2") {
print.default_object_config.support_material_contact_distance = 0.2;
print.add_model_object(model.objects[0]);
bool check = test_6_checks(print);
REQUIRE(check == true); // bridge speed is used.
}
WHEN("support_material_contact_distance = 0") {
print.default_object_config.support_material_contact_distance = 0;
print.add_model_object(model.objects[0]);
bool check = test_6_checks(print);
REQUIRE(check == true); // bridge speed is not used.
}
WHEN("support_material_contact_distance = 0.2 & raft_layers = 5") {
print.default_object_config.support_material_contact_distance = 0.2;
print.default_object_config.raft_layers = 5;
print.add_model_object(model.objects[0]);
bool check = test_6_checks(print);
REQUIRE(check == true); // bridge speed is used.
}
WHEN("support_material_contact_distance = 0 & raft_layers = 5") {
print.default_object_config.support_material_contact_distance = 0;
print.default_object_config.raft_layers = 5;
print.add_model_object(model.objects[0]);
bool check = test_6_checks(print);
REQUIRE(check == true); // bridge speed is not used.
}
}
}
#endif

View File

@ -384,20 +384,21 @@ SCENARIO( "TriangleMeshSlicer: Cut behavior.") {
#ifdef TEST_PERFORMANCE
TEST_CASE("Regression test for issue #4486 - files take forever to slice") {
TriangleMesh mesh;
std::shared_ptr<Slic3r::DynamicPrintConfig> config = Slic3r::DynamicPrintConfig::new_from_defaults();
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
mesh.ReadSTLFile(std::string(testfile_dir) + "test_trianglemesh/4486/100_000.stl");
mesh.repair();
config->set("layer_height", 500);
config->set("first_layer_height", 250);
config->set("nozzle_diameter", 500);
config.set("layer_height", 500);
config.set("first_layer_height", 250);
config.set("nozzle_diameter", 500);
Slic3r::Print print;
Slic3r::Model model;
auto print = Slic3r::Test::init_print({mesh}, model, config);
Slic3r::Test::init_print({mesh}, print, model, config);
print->status_cb = [] (int ln, const std::string& msg) { Slic3r::Log::info("Print") << ln << " " << msg << "\n";};
print.status_cb = [] (int ln, const std::string& msg) { Slic3r::Log::info("Print") << ln << " " << msg << "\n";};
std::future<void> fut = std::async([&print] () { print->process(); });
std::future<void> fut = std::async([&print] () { print.process(); });
std::chrono::milliseconds span {120000};
bool timedout {false};
if(fut.wait_for(span) == std::future_status::timeout) {
@ -411,21 +412,22 @@ TEST_CASE("Regression test for issue #4486 - files take forever to slice") {
#ifdef BUILD_PROFILE
TEST_CASE("Profile test for issue #4486 - files take forever to slice") {
TriangleMesh mesh;
std::shared_ptr<Slic3r::DynamicPrintConfig> config = Slic3r::DynamicPrintConfig::new_from_defaults();
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
mesh.ReadSTLFile(std::string(testfile_dir) + "test_trianglemesh/4486/10_000.stl");
mesh.repair();
config->set("layer_height", 500);
config->set("first_layer_height", 250);
config->set("nozzle_diameter", 500);
config->set("fill_density", "5%");
config.set("layer_height", 500);
config.set("first_layer_height", 250);
config.set("nozzle_diameter", 500);
config.set("fill_density", "5%");
Slic3r::Print print;
Slic3r::Model model;
auto print = Slic3r::Test::init_print({mesh}, model, config);
Slic3r::Test::init_print({mesh}, print, model, config);
print->status_cb = [] (int ln, const std::string& msg) { Slic3r::Log::info("Print") << ln << " " << msg << "\n";};
print.status_cb = [] (int ln, const std::string& msg) { Slic3r::Log::info("Print") << ln << " " << msg << "\n";};
print->process();
print.process();
REQUIRE(true);

View File

@ -1,6 +1,8 @@
get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
add_executable(${_TEST_NAME}_tests
${_TEST_NAME}_tests.cpp
test_3mf.cpp
test_config.cpp
test_geometry.cpp
test_polygon.cpp
)

View File

@ -0,0 +1,20 @@
#include <catch2/catch.hpp>
#include "libslic3r/Model.hpp"
#include "libslic3r/Format/3mf.hpp"
using namespace Slic3r;
SCENARIO("Reading 3mf file") {
GIVEN("umlauts in the path of the file") {
Slic3r::Model model;
WHEN("3mf model is read") {
std::string path = std::string(TEST_DATA_DIR) + "/test_3mf/Geräte/Büchse.3mf";
DynamicPrintConfig config;
bool ret = Slic3r::load_3mf(path.c_str(), &config, &model, false);
THEN("load should succeed") {
REQUIRE(ret);
}
}
}
}

View File

@ -0,0 +1,203 @@
#include <catch2/catch.hpp>
#include "libslic3r/PrintConfig.hpp"
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") {
config.set_deserialize("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") {
config.set("perimeter_extrusion_width", -10);
THEN( "Validate returns error") {
REQUIRE(! config.validate().empty());
}
}
WHEN( "perimeters is set to -10, an invalid value") {
config.set("perimeters", -10);
THEN( "Validate returns error") {
REQUIRE(! config.validate().empty());
}
}
}
}
SCENARIO("Config accessor functions perform as expected.", "[Config]") {
GIVEN("A config generated from default options") {
Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_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.") {
REQUIRE(config.opt<ConfigOptionBool>("gcode_comments")->getBool() == true);
}
}
WHEN("A boolean option is set to a string value representing a 0 or 1") {
CHECK_NOTHROW(config.set_deserialize("gcode_comments", "1"));
THEN("The underlying value is set correctly.") {
REQUIRE(config.opt<ConfigOptionBool>("gcode_comments")->getBool() == true);
}
}
WHEN("A boolean option is set to a string value representing something other than 0 or 1") {
THEN("A BadOptionTypeException exception is thrown.") {
REQUIRE_THROWS_AS(config.set("gcode_comments", "Z"), BadOptionTypeException);
}
AND_THEN("Value is unchanged.") {
REQUIRE(config.opt<ConfigOptionBool>("gcode_comments")->getBool() == false);
}
}
WHEN("A boolean option is set to an int value") {
THEN("A BadOptionTypeException exception is thrown.") {
REQUIRE_THROWS_AS(config.set("gcode_comments", 1), BadOptionTypeException);
}
}
WHEN("A numeric option is set from serialized string") {
config.set_deserialize("bed_temperature", "100");
THEN("The underlying value is set correctly.") {
REQUIRE(config.opt<ConfigOptionInts>("bed_temperature")->get_at(0) == 100);
}
}
#if 0
//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);
}
}
#endif
WHEN("An floating-point option is set through the integer interface") {
config.set("perimeter_speed", 10);
THEN("The underlying value is set correctly.") {
REQUIRE(config.opt<ConfigOptionFloat>("perimeter_speed")->getFloat() == 10.0);
}
}
WHEN("A floating-point option is set through the double interface") {
config.set("perimeter_speed", 5.5);
THEN("The underlying value is set correctly.") {
REQUIRE(config.opt<ConfigOptionFloat>("perimeter_speed")->getFloat() == 5.5);
}
}
WHEN("An integer-based option is set through the double interface") {
THEN("A BadOptionTypeException exception is thrown.") {
REQUIRE_THROWS_AS(config.set("bed_temperature", 5.5), BadOptionTypeException);
}
}
WHEN("A numeric option is set to a non-numeric value.") {
THEN("A BadOptionTypeException exception is thown.") {
REQUIRE_THROWS_AS(config.set_deserialize("perimeter_speed", "zzzz"), BadOptionTypeException);
}
THEN("The value does not change.") {
REQUIRE(config.opt<ConfigOptionFloat>("perimeter_speed")->getFloat() == 60.0);
}
}
WHEN("A string option is set through the string interface") {
config.set("printhost_apikey", "100");
THEN("The underlying value is set correctly.") {
REQUIRE(config.opt<ConfigOptionString>("printhost_apikey")->value == "100");
}
}
WHEN("A string option is set through the integer interface") {
config.set("printhost_apikey", 100);
THEN("The underlying value is set correctly.") {
REQUIRE(config.opt<ConfigOptionString>("printhost_apikey")->value == "100");
}
}
WHEN("A string option is set through the double interface") {
config.set("printhost_apikey", 100.5);
THEN("The underlying value is set correctly.") {
REQUIRE(config.opt<ConfigOptionString>("printhost_apikey")->value == std::to_string(100.5));
}
}
WHEN("A float or percent is set as a percent through the string interface.") {
config.set_deserialize("first_layer_extrusion_width", "100%");
THEN("Value and percent flag are 100/true") {
auto tmp = config.opt<ConfigOptionFloatOrPercent>("first_layer_extrusion_width");
REQUIRE(tmp->percent == true);
REQUIRE(tmp->value == 100);
}
}
WHEN("A float or percent is set as a float through the string interface.") {
config.set_deserialize("first_layer_extrusion_width", "100");
THEN("Value and percent flag are 100/false") {
auto tmp = config.opt<ConfigOptionFloatOrPercent>("first_layer_extrusion_width");
REQUIRE(tmp->percent == false);
REQUIRE(tmp->value == 100);
}
}
WHEN("A float or percent is set as a float through the int interface.") {
config.set("first_layer_extrusion_width", 100);
THEN("Value and percent flag are 100/false") {
auto tmp = config.opt<ConfigOptionFloatOrPercent>("first_layer_extrusion_width");
REQUIRE(tmp->percent == false);
REQUIRE(tmp->value == 100);
}
}
WHEN("A float or percent is set as a float through the double interface.") {
config.set("first_layer_extrusion_width", 100.5);
THEN("Value and percent flag are 100.5/false") {
auto tmp = config.opt<ConfigOptionFloatOrPercent>("first_layer_extrusion_width");
REQUIRE(tmp->percent == false);
REQUIRE(tmp->value == 100.5);
}
}
WHEN("An invalid option is requested during set.") {
THEN("A BadOptionTypeException exception is thrown.") {
REQUIRE_THROWS_AS(config.set("deadbeef_invalid_option", 1), UnknownOptionException);
REQUIRE_THROWS_AS(config.set("deadbeef_invalid_option", 1.0), UnknownOptionException);
REQUIRE_THROWS_AS(config.set("deadbeef_invalid_option", "1"), UnknownOptionException);
REQUIRE_THROWS_AS(config.set("deadbeef_invalid_option", true), UnknownOptionException);
}
}
WHEN("An invalid option is requested during get.") {
THEN("A UnknownOptionException exception is thrown.") {
REQUIRE_THROWS_AS(config.option_throw<ConfigOptionString>("deadbeef_invalid_option", false), UnknownOptionException);
REQUIRE_THROWS_AS(config.option_throw<ConfigOptionFloat>("deadbeef_invalid_option", false), UnknownOptionException);
REQUIRE_THROWS_AS(config.option_throw<ConfigOptionInt>("deadbeef_invalid_option", false), UnknownOptionException);
REQUIRE_THROWS_AS(config.option_throw<ConfigOptionBool>("deadbeef_invalid_option", false), UnknownOptionException);
}
}
WHEN("An invalid option is requested during opt.") {
THEN("A UnknownOptionException exception is thrown.") {
REQUIRE_THROWS_AS(config.option_throw<ConfigOptionString>("deadbeef_invalid_option", false), UnknownOptionException);
REQUIRE_THROWS_AS(config.option_throw<ConfigOptionFloat>("deadbeef_invalid_option", false), UnknownOptionException);
REQUIRE_THROWS_AS(config.option_throw<ConfigOptionInt>("deadbeef_invalid_option", false), UnknownOptionException);
REQUIRE_THROWS_AS(config.option_throw<ConfigOptionBool>("deadbeef_invalid_option", false), UnknownOptionException);
}
}
WHEN("getX called on an unset option.") {
THEN("The default is returned.") {
REQUIRE(config.opt_float("layer_height") == 0.3);
REQUIRE(config.opt_int("raft_layers") == 0);
REQUIRE(config.opt_bool("support_material") == false);
}
}
WHEN("getFloat called on an option that has been set.") {
config.set("layer_height", 0.5);
THEN("The set value is returned.") {
REQUIRE(config.opt_float("layer_height") == 0.5);
}
}
}
}
SCENARIO("Config ini load/save interface", "[Config]") {
WHEN("new_from_ini is called") {
Slic3r::DynamicPrintConfig config;
std::string path = std::string(TEST_DATA_DIR) + "/test_config/new_from_ini.ini";
config.load_from_ini(path);
THEN("Config object contains ini file options.") {
REQUIRE(config.option_throw<ConfigOptionStrings>("filament_colour", false)->values.size() == 1);
REQUIRE(config.option_throw<ConfigOptionStrings>("filament_colour", false)->values.front() == "#ABCD");
}
}
}

View File

@ -299,7 +299,7 @@ bool ConfigBase__set_deserialize(ConfigBase* THIS, const t_config_option_key &op
size_t len;
const char * c = SvPV(str, len);
std::string value(c, len);
return THIS->set_deserialize(opt_key, value);
return THIS->set_deserialize_nothrow(opt_key, value);
}
void ConfigBase__set_ifndef(ConfigBase* THIS, const t_config_option_key &opt_key, SV* value, bool deserialize)

View File

@ -8,7 +8,8 @@
%name{Slic3r::Config} class DynamicPrintConfig {
DynamicPrintConfig();
~DynamicPrintConfig();
static DynamicPrintConfig* new_from_defaults();
static DynamicPrintConfig* new_from_defaults()
%code{% RETVAL = DynamicPrintConfig::new_from_defaults_keys(FullPrintConfig::defaults().keys()); %};
static DynamicPrintConfig* new_from_defaults_keys(std::vector<std::string> keys);
DynamicPrintConfig* clone() %code{% RETVAL = new DynamicPrintConfig(*THIS); %};
DynamicPrintConfig* clone_only(std::vector<std::string> keys)