Add some cpp test from mainline (thanks to @lordofhyphens)

* to build the tests on windows, you need vs2015 or 2017 with "CRT SDK"
so  you have to also build all deps & slic3r against this compiler.
 * I didn't tried to build the tests on linux/osx, maybe some cmkae changes are needed.
 * I added a slic3r_lib project to be able to link statically the test against all the slic3r project.
 * tests will need many changes to be able to use the new codebase. only the ones in the cmakelists are done right now.
 * some test may be in error, because the behavior of the test vs slic3r isn't defined well enough to make a decision of which one is wrong.
This commit is contained in:
supermerill 2019-03-29 18:02:25 +01:00
parent 19d471f64b
commit 16368c9575
65 changed files with 10170 additions and 28 deletions

2
.gitignore vendored
View File

@ -12,4 +12,4 @@ xs/assertlib*
.init_bundle.ini
local-lib
build*
deps/deps-build
deps/deps-*

View File

@ -100,3 +100,8 @@ The dependency build will by default build _both_ the _Release_ and _Debug_ vari
You can disable building of the debug variant by passing the `-DDEP_DEBUG=OFF` option to CMake, this will only produce a _Release_ build.
Refer to the CMake scripts inside the `deps` directory to see which dependencies are built in what versions and how this is done.
### building tests
You must use vs 2015 or 2017, and convert the slic3r_test project to your version. Also, you have to add the "CRT SDK" to your windows 2017 installation (can be done via the VS intaller).

View File

@ -13,7 +13,7 @@ add_subdirectory(qhull)
add_subdirectory(Shiny)
add_subdirectory(semver)
add_subdirectory(imgui)
#add_subdirectory(test)
add_subdirectory(test)
# Adding libnest2d project for bin packing...
set(LIBNEST2D_UNITTESTS ON CACHE BOOL "Force generating unittests for libnest2d")
@ -94,6 +94,7 @@ if (MSVC)
else ()
add_executable(slic3r slic3r.cpp slic3r.hpp)
endif ()
add_library(slic3r_lib STATIC slic3r.cpp slic3r.hpp)
if (NOT MSVC)
if(SLIC3R_GUI)
set_target_properties(slic3r PROPERTIES OUTPUT_NAME "slic3r-gui")
@ -103,31 +104,38 @@ if (NOT MSVC)
endif ()
target_link_libraries(slic3r libslic3r)
target_link_libraries(slic3r_lib libslic3r)
if (APPLE)
# add_compile_options(-stdlib=libc++)
# add_definitions(-DBOOST_THREAD_DONT_USE_CHRONO -DBOOST_NO_CXX11_RVALUE_REFERENCES -DBOOST_THREAD_USES_MOVE)
# -liconv: boost links to libiconv by default
target_link_libraries(slic3r "-liconv -framework IOKit" "-framework CoreFoundation" -lc++)
target_link_libraries(slic3r_lib "-liconv -framework IOKit" "-framework CoreFoundation" -lc++)
elseif (MSVC)
# Manifest is provided through slic3r.rc, don't generate your own.
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /MANIFEST:NO")
else ()
target_link_libraries(slic3r ${CMAKE_DL_LIBS} -lstdc++)
target_link_libraries(slic3r_lib ${CMAKE_DL_LIBS} -lstdc++)
endif ()
# Add the Slic3r GUI library, libcurl, OpenGL and GLU libraries.
if (SLIC3R_GUI)
target_link_libraries(slic3r libslic3r_gui ${wxWidgets_LIBRARIES})
target_link_libraries(slic3r_lib libslic3r_gui ${wxWidgets_LIBRARIES})
# Configure libcurl & OpenSSL
find_package(CURL REQUIRED)
target_include_directories(slic3r PRIVATE ${CURL_INCLUDE_DIRS})
target_link_libraries(slic3r CURL::libcurl)
target_include_directories(slic3r_lib PRIVATE ${CURL_INCLUDE_DIRS})
target_link_libraries(slic3r_lib CURL::libcurl)
if (SLIC3R_STATIC)
if (NOT APPLE)
# libcurl is always linked dynamically to the system libcurl on OSX.
# On other systems, libcurl is linked statically if SLIC3R_STATIC is set.
target_compile_definitions(slic3r PRIVATE CURL_STATICLIB)
target_compile_definitions(slic3r_lib PRIVATE CURL_STATICLIB)
endif()
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
# As of now, our build system produces a statically linked libcurl,
@ -137,17 +145,23 @@ if (SLIC3R_GUI)
message("OpenSSL libraries: ${OPENSSL_LIBRARIES}")
target_include_directories(slic3r PRIVATE ${OPENSSL_INCLUDE_DIR})
target_link_libraries(slic3r ${OPENSSL_LIBRARIES})
target_include_directories(slic3r_lib PRIVATE ${OPENSSL_INCLUDE_DIR})
target_link_libraries(slic3r_lib ${OPENSSL_LIBRARIES})
endif()
endif()
if (MSVC)
target_link_libraries(slic3r user32.lib Setupapi.lib OpenGL32.Lib GlU32.Lib)
target_link_libraries(slic3r_lib user32.lib Setupapi.lib OpenGL32.Lib GlU32.Lib)
elseif (MINGW)
target_link_libraries(slic3r -lopengl32)
target_link_libraries(slic3r_lib -lopengl32)
elseif (APPLE)
target_link_libraries(slic3r "-framework OpenGL")
target_link_libraries(slic3r_lib "-framework OpenGL")
else ()
target_link_libraries(slic3r -ldl -lGL -lGLU)
target_link_libraries(slic3r_lib -ldl -lGL -lGLU)
endif ()
endif ()
@ -164,10 +178,6 @@ if (MSVC)
add_dependencies(slic3r_app_console slic3r)
set_target_properties(slic3r_app_console PROPERTIES OUTPUT_NAME "slic3r-console")
# add_executable(slic3r_test slic3r.cpp ${CMAKE_CURRENT_BINARY_DIR}/slic3r.rc)
# target_compile_definitions(slic3r_test PRIVATE -DSLIC3R_WRAPPER_CONSOLE)
# add_dependencies(slic3r_test slic3r)
# set_target_properties(slic3r_test PROPERTIES OUTPUT_NAME "slic3r-test")
endif ()
# Link the resources dir to where Slic3r GUI expects it
@ -215,7 +225,6 @@ if (WIN32)
if (MSVC)
install(TARGETS slic3r_app_gui RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}")
install(TARGETS slic3r_app_console RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}")
# install(TARGETS slic3r_test RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}")
endif ()
else ()
install(TARGETS slic3r RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")

File diff suppressed because it is too large Load Diff

View File

@ -342,8 +342,13 @@ void ConfigBase::apply_only(const ConfigBase &other, const t_config_option_keys
if (other_opt == nullptr) {
// The key was not found in the source config, therefore it will not be initialized!
// printf("Not found, therefore not initialized: %s\n", opt_key.c_str());
} else
} else {
try {
my_opt->set(other_opt);
} catch (ConfigurationException e) {
throw ConfigurationException(std::string(e.what()) + ", when ConfigBase::apply_only on " + opt_key);
}
}
}
}

View File

@ -46,6 +46,15 @@ public:
NoDefinitionException(const std::string &opt_key) :
std::runtime_error(std::string("No definition exception: ") + opt_key) {}
};
// a bit more specific than a runtime_error
class ConfigurationException : public std::runtime_error
{
public:
ConfigurationException() :
std::runtime_error("Configuration exception") {}
ConfigurationException(const std::string &opt_key) :
std::runtime_error(std::string("Configuration exception: ") + opt_key) {}
};
// Type of a configuration value.
enum ConfigOptionType {
@ -650,7 +659,7 @@ public:
void set(const ConfigOption *rhs) override {
if (rhs->type() != this->type())
throw std::runtime_error("ConfigOptionFloatOrPercent: Assigning an incompatible type");
throw ConfigurationException("ConfigOptionFloatOrPercent: Assigning an incompatible type");
assert(dynamic_cast<const ConfigOptionFloatOrPercent*>(rhs));
*this = *static_cast<const ConfigOptionFloatOrPercent*>(rhs);
}

View File

@ -12,9 +12,9 @@ static inline float auto_extrusion_width(FlowRole role, float nozzle_diameter, f
case frSupportMaterial:
case frSupportMaterialInterface:
case frTopSolidInfill:
return nozzle_diameter;
default:
case frExternalPerimeter:
return 1.05f * nozzle_diameter;
default:
case frPerimeter:
case frSolidInfill:
case frInfill:

View File

@ -841,20 +841,19 @@ bool load_amf(const char *path, DynamicPrintConfig *config, Model *model)
return false;
}
bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config)
bool store_amf(std::string &path, Model *model, const DynamicPrintConfig *config)
{
if ((path == nullptr) || (model == nullptr))
if ((path.empty()) || (model == nullptr))
return false;
// forces ".zip.amf" extension
std::string export_path = path;
if (!boost::iends_with(export_path, ".zip.amf"))
export_path = boost::filesystem::path(export_path).replace_extension(".zip.amf").string();
if (!boost::iends_with(path, ".zip.amf"))
path = boost::filesystem::path(path).replace_extension(".zip.amf").string();
mz_zip_archive archive;
mz_zip_zero_struct(&archive);
mz_bool res = mz_zip_writer_init_file(&archive, export_path.c_str(), 0);
mz_bool res = mz_zip_writer_init_file(&archive, path.c_str(), 0);
if (res == 0)
return false;
@ -1013,20 +1012,20 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config)
}
stream << "</amf>\n";
std::string internal_amf_filename = boost::ireplace_last_copy(boost::filesystem::path(export_path).filename().string(), ".zip.amf", ".amf");
std::string internal_amf_filename = boost::ireplace_last_copy(boost::filesystem::path(path).filename().string(), ".zip.amf", ".amf");
std::string out = stream.str();
if (!mz_zip_writer_add_mem(&archive, internal_amf_filename.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION))
{
mz_zip_writer_end(&archive);
boost::filesystem::remove(export_path);
boost::filesystem::remove(path);
return false;
}
if (!mz_zip_writer_finalize_archive(&archive))
{
mz_zip_writer_end(&archive);
boost::filesystem::remove(export_path);
boost::filesystem::remove(path);
return false;
}

View File

@ -11,7 +11,7 @@ extern bool load_amf(const char *path, DynamicPrintConfig *config, Model *model)
// Save the given model and the config data into an amf file.
// The model could be modified during the export process if meshes are not repaired or have no shared vertices
extern bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config);
extern bool store_amf(std::string &path, Model *model, const DynamicPrintConfig *config);
}; // namespace Slic3r

View File

@ -94,6 +94,7 @@ public:
template<typename OtherDerived>
Point(const Eigen::MatrixBase<OtherDerived> &other) : Vec2crd(other) {}
static Point new_scale(coordf_t x, coordf_t y) { return Point(coord_t(scale_(x)), coord_t(scale_(y))); }
static Point new_scale(const Point &p) { return Point(coord_t(scale_(p.x())), coord_t(scale_(p.y()))); }
// This method allows you to assign Eigen expressions to MyVectorType
template<typename OtherDerived>

View File

@ -555,7 +555,7 @@ bool CLI::setup(int argc, char **argv)
void CLI::print_help(bool include_print_options, PrinterTechnology printer_technology) const
{
boost::nowide::cout
<< "Slic3r Prusa Edition " << SLIC3R_BUILD << std::endl
<< "Slic3r++ " << SLIC3R_BUILD << std::endl
<< "https://github.com/prusa3d/Slic3r" << std::endl << std::endl
<< "Usage: slic3r [ ACTIONS ] [ TRANSFORM ] [ OPTIONS ] [ file.stl ... ]" << std::endl
<< std::endl
@ -586,10 +586,10 @@ void CLI::print_help(bool include_print_options, PrinterTechnology printer_techn
bool CLI::export_models(IO::ExportFormat format)
{
for (Model &model : m_models) {
const std::string path = this->output_filepath(model, format);
std::string path = this->output_filepath(model, format);
bool success = false;
switch (format) {
case IO::AMF: success = Slic3r::store_amf(path.c_str(), &model, nullptr); break;
case IO::AMF: success = Slic3r::store_amf(path, &model, nullptr); break;
case IO::OBJ: success = Slic3r::store_obj(path.c_str(), &model); break;
case IO::STL: success = Slic3r::store_stl(path.c_str(), &model, true); break;
case IO::TMF: success = Slic3r::store_3mf(path.c_str(), &model, nullptr); break;

View File

@ -21,6 +21,8 @@ class CLI {
public:
int run(int argc, char **argv);
DynamicPrintConfig& full_print_config() { return m_print_config; }
private:
DynamicPrintAndCLIConfig m_config;
DynamicPrintConfig m_print_config;

View File

@ -3272,11 +3272,11 @@ void Plater::export_amf()
if (! dialog) { return; }
const wxString path = dialog->GetPath();
const std::string path_u8 = into_u8(path);
std::string path_u8 = into_u8(path);
DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure();
wxBusyCursor wait;
if (Slic3r::store_amf(path_u8.c_str(), &p->model, dialog->get_checkbox_value() ? &cfg : nullptr)) {
if (Slic3r::store_amf(path_u8, &p->model, dialog->get_checkbox_value() ? &cfg : nullptr)) {
// Success
p->statusbar()->set_status_text(wxString::Format(_(L("AMF file exported to %s")), path));
} else {

58
src/test/CMakeLists.txt Normal file
View File

@ -0,0 +1,58 @@
project(slic3r_test)
cmake_minimum_required(VERSION 2.6)
include(PrecompiledHeader)
set(TESTDIR ${CMAKE_CURRENT_SOURCE_DIR})
set(TESTFILE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/inputs/)
set(SLIC3R_TEST_SOURCES
test_data.cpp
test_data.hpp
test_harness.cpp
GUI/test_cli.cpp
# libslic3r/test_config.cpp # toredo
libslic3r/test_fill.cpp
libslic3r/test_flow.cpp
)
if (NOT TARGET Catch)
include (ExternalProject)
if(IS_TRAVIS_BUILD) # on travis, use git for fetching instead of wget
set(FETCH_EXTERNAL_CATCH
GIT_REPOSITORY https://github.com/catchorg/Catch.git
GIT_TAG 03d122a35c3f5c398c43095a87bc82ed44642516)
elseif(WIN32)
set(FETCH_EXTERNAL_CATCH
URL https://github.com/catchorg/Catch2/archive/v2.4.2.zip
URL_HASH MD5=6a2ffb9c69d368ebc1ad13146c5a5e1e)
else()
set(FETCH_EXTERNAL_CATCH
URL https://github.com/catchorg/Catch2/archive/v2.4.2.tar.gz
URL_HASH MD5=26927b878b1f42633f15a9ef1c4bd8e7)
endif()
ExternalProject_Add(Catch-External
PREFIX ${CMAKE_BINARY_DIR}/external/Catch
${FETCH_EXTERNAL_CATCH}
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/external/Catch/src/Catch-External/single_include/catch2/catch.hpp
${CMAKE_BINARY_DIR}/external/Catch/include/catch.hpp
)
add_library(Catch INTERFACE)
add_dependencies(Catch Catch-External)
target_include_directories(Catch INTERFACE ${CMAKE_BINARY_DIR}/external/Catch/include)
target_compile_definitions(Catch INTERFACE $<$<CXX_COMPILER_ID:MSVC>:_SILENCE_CXX17_UNCAUGHT_EXCEPTION_DEPRECATION_WARNING>)
endif ()
configure_file("${TESTDIR}/test_options.hpp.in" "${TESTDIR}/test_options.hpp")
add_executable(slic3r_test ${SLIC3R_TEST_SOURCES})
add_test(NAME TestSlic3r COMMAND slic3r_test)
target_compile_features(slic3r_test PUBLIC cxx_std_14)
# add_library(slic3r_test STATIC ${SLIC3R_TEST_SOURCES})
# target_link_libraries(slic3r_test PUBLIC libslic3r Catch ${LIBSLIC3R_DEPENDS})
target_link_libraries(slic3r_test slic3r_lib Catch)
# libnest2d admesh miniz ${Boost_LIBRARIES} clipper nowide ${EXPAT_LIBRARIES} ${GLEW_LIBRARIES} ${PNG_LIBRARIES} glu-libtess polypartition poly2tri qhull semver tbb
# if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)
# add_precompiled_header(slic3r_test libslic3r_gui pchheader.hpp FORCEINCLUDE)
# endif ()

286
src/test/GUI/test_cli.cpp Normal file
View File

@ -0,0 +1,286 @@
#include <catch.hpp>
#include <fstream>
#include <cstdio>
#include "../test_options.hpp"
#include "../../slic3r.hpp"
#include "../../libslic3r/GCodeReader.hpp"
using namespace Slic3r;
using namespace std::string_literals;
bool file_exists(const std::string& name, const std::string& ext) {
std::string filename {""};
filename.append(name);
filename.append(".");
filename.append(ext);
std::ifstream f(testfile(filename));
bool result {f.good()};
f.close();
return result;
}
char** to_cstr_array(std::vector<std::string> in, char** argv) {
int i = 0;
for (auto& str : in) {
argv[i] = new char[str.size()];
strcpy(argv[i], str.c_str());
i++;
}
return argv;
}
void clean_array(size_t argc, char** argv) {
//for (size_t i = 0; i < argc; ++i) {
// delete[] argv[i];
//}
}
SCENARIO( "CLI Export Arguments", "[!mayfail]") {
char* args_cli[20];
std::vector<std::string> in_args;
in_args.reserve(20);
in_args.emplace_back("gui_test"s);
in_args.emplace_back(testfile("test_cli/20mmbox.stl"s));
GIVEN( "Default configuration and a simple 3D model" ) {
WHEN ( "[ ACTION ] is export-gcode with long option") {
in_args.emplace(in_args.cend()-1, "--export-gcode");
CLI().run(in_args.size(), to_cstr_array(in_args, args_cli));
THEN ("GCode file is created.") {
REQUIRE(file_exists("test_cli/20mmbox"s, "gcode"s));
}
clean_array(in_args.size(), args_cli);
clean_file("test_cli/20mmbox", "gcode");
}
WHEN ( "[ ACTION ] is export-gcode with short option") {
in_args.emplace(in_args.cend()-1, "-g");
CLI().run(in_args.size(), to_cstr_array(in_args, args_cli));
THEN ("GCode file is created.") {
REQUIRE(file_exists("test_cli/20mmbox"s, "gcode"s));
}
clean_array(in_args.size(), args_cli);
clean_file("test_cli/20mmbox", "gcode");
}
// doesn't work anymore
//WHEN ( "[ ACTION ] is export-obj") {
// in_args.emplace(in_args.cend()-1, "--export-obj");
// CLI().run(in_args.size(), to_cstr_array(in_args, args_cli));
// THEN ("OBJ file is created.") {
// REQUIRE(file_exists("test_cli/20mmbox"s, "obj"s));
// }
// clean_array(in_args.size(), args_cli);
// clean_file("test_cli/20mmbox", "obj");
//}
//doesn't work
//WHEN ( "[ ACTION ] is export-pov") {
// in_args.emplace(in_args.cend()-1, "--export-pov");
// CLI().run(in_args.size(), to_cstr_array(in_args, args_cli));
// THEN ("POV file is created.") {
// REQUIRE(file_exists("test_cli/20mmbox", "pov"));
// }
// clean_array(in_args.size(), args_cli);
// clean_file("test_cli/20mmbox", "pov");
//}
WHEN ( "[ ACTION ] is export-amf") {
in_args.emplace(in_args.cend()-1, "--export-amf");
CLI().run(in_args.size(), to_cstr_array(in_args, args_cli));
THEN ("Compressed AMF file is created.") {
REQUIRE(file_exists("test_cli/20mmbox.zip", "amf"));
}
clean_array(in_args.size(), args_cli);
clean_file("test_cli/20mmbox.zip", "amf");
}
WHEN ( "[ ACTION ] is export-3mf") {
in_args.emplace(in_args.cend()-1, "--export-3mf");
CLI().run(in_args.size(), to_cstr_array(in_args, args_cli));
THEN ("3MF file is created.") {
REQUIRE(file_exists("test_cli/20mmbox", "3mf"));
}
clean_array(in_args.size(), args_cli);
clean_file("test_cli/20mmbox", "3mf");
}
// now export-sla
//WHEN ( "[ ACTION ] is export-svg") {
// in_args.emplace(in_args.cend()-1, "--export-svg");
// CLI().run(in_args.size(), to_cstr_array(in_args, args_cli));
// THEN ("SVG files are created.") {
// REQUIRE(file_exists("test_cli/20mmbox_0", "svg"));
// REQUIRE(file_exists("test_cli/20mmbox_1", "svg"));
// REQUIRE(file_exists("test_cli/20mmbox_2", "svg"));
// REQUIRE(file_exists("test_cli/20mmbox_3", "svg"));
// REQUIRE(file_exists("test_cli/20mmbox_4", "svg"));
// }
// clean_file("test_cli/20mmbox_0", "svg", true);
// clean_file("test_cli/20mmbox_1", "svg", true);
// clean_file("test_cli/20mmbox_2", "svg", true);
// clean_file("test_cli/20mmbox_3", "svg", true);
// clean_file("test_cli/20mmbox_4", "svg", true);
// clean_array(in_args.size(), args_cli);
//}
//WHEN ( "[ ACTION ] is export-sla-svg") {
// in_args.emplace(in_args.cend()-1, "--export-sla-svg");
// CLI().run(in_args.size(), to_cstr_array(in_args, args_cli));
// THEN ("SVG files are created.") {
// REQUIRE(file_exists("test_cli/20mmbox", "svg"));
// }
// clean_array(in_args.size(), args_cli);
// clean_file("test_cli/20mmbox", "svg", true);
//}
//WHEN("[ ACTION ] is sla") {
// in_args.emplace(in_args.cend() - 1, "--sla");
// CLI().run(in_args.size(), to_cstr_array(in_args, args_cli));
// THEN("SVG file is created.") {
// REQUIRE(file_exists("test_cli/20mmbox", "svg"));
// }
// clean_array(in_args.size(), args_cli);
// clean_file("test_cli/20mmbox", "svg", true);
//}
//WHEN("[ ACTION ] is sla for sl1") {
// in_args.emplace(in_args.cend() - 1, "--sla");
// CLI().run(in_args.size(), to_cstr_array(in_args, args_cli));
// THEN("SVG file is created.") {
// REQUIRE(file_exists("test_cli/20mmbox", "sl1"));
// }
// clean_array(in_args.size(), args_cli);
// clean_file("test_cli/20mmbox", "sl1", true);
//}
//WHEN ( "[ ACTION ] is sla and --output is output.svg") {
// in_args.emplace(in_args.cend()-1, "--sla");
// in_args.emplace(in_args.cend()-1, "--output");
// in_args.emplace(in_args.cend()-1, testfile("output.svg"));
// CLI().run(in_args.size(), to_cstr_array(in_args, args_cli));
// THEN ("SVG files are created.") {
// REQUIRE(file_exists("output", "svg"));
// }
// clean_array(in_args.size(), args_cli);
// clean_file("output", "svg", true);
//}
WHEN ( "[ ACTION ] is save") {
in_args.emplace(in_args.cend()-1, "--save");
in_args.emplace(in_args.cend()-1, testfile("cfg.ini"));
CLI().run(in_args.size(), to_cstr_array(in_args, args_cli));
THEN ("Configuration file is created.") {
REQUIRE(file_exists("cfg", "ini"));
}
clean_array(in_args.size(), args_cli);
clean_file("cfg", "ini");
}
WHEN ( "[ ACTION ] is export-stl and --output option is specified") {
in_args.emplace(in_args.cend()-1, "--export-stl");
in_args.emplace(in_args.cend()-1, "--output");
in_args.emplace(in_args.cend()-1, testfile("output.stl"));
CLI().run(in_args.size(), to_cstr_array(in_args, args_cli));
THEN ("STL file is created.") {
REQUIRE(file_exists("output", "stl"));
}
clean_array(in_args.size(), args_cli);
clean_file("output", "stl");
}
}
}
// ??
//SCENARIO("CLI Transform arguments", "[!shouldfail]") {
// char* args_cli[20];
// std::vector<std::string> in_args;
// in_args.reserve(20);
// in_args.emplace_back("gui_test"s);
// in_args.emplace_back(testfile("test_cli/20mmbox.stl"s));
// WHEN("Tests are implemented for CLI model transform") {
// THEN ("Tests should not fail :D") {
// REQUIRE(false);
// }
// }
//}
// Test the --center and --dont-arrange parameters.
SCENARIO("CLI positioning arguments") {
char* args_cli[20];
std::vector<std::string> in_args;
in_args.reserve(20);
in_args.emplace_back("gui_test"s);
GIVEN( " 3D Model for a 20mm box, centered around 0,0 and gcode export" ) {
in_args.emplace_back(testfile("test_cli/20mmbox.stl"s));
in_args.emplace(in_args.cend()-1, "-g");
in_args.emplace(in_args.cend()-1, "--load"s);
in_args.emplace(in_args.cend()-1, testfile("test_cli/20mmbox_config.ini"));
CLI cut;
WHEN("--center is supplied with 40,40") {
in_args.emplace(in_args.cend()-1, "--center");
in_args.emplace(in_args.cend()-1, "40,40");
cut.run(in_args.size(), to_cstr_array(in_args, args_cli));
THEN ("The first layer of the print should be centered around 0,0") {
std::string exported { read_to_string("test_cli/20mmbox.gcode"s)};
GCodeReader reader;
REQUIRE(exported != ""s);
double min_x = 50.0, max_x = -50.0, min_y = 50.0, max_y = -50.0;
reader.apply_config(cut.full_print_config());
reader.parse_buffer(exported, [&min_x, &min_y, &max_x, &max_y] (GCodeReader& self, const GCodeReader::GCodeLine& line)
{
if (self.x() != 0.0 && self.y() != 0.0) { // avoid the first pass
if (self.z() <= 0.6 && self.z() > 0.3) {
min_x = std::min(min_x, static_cast<double>(self.x()));
min_y = std::min(min_y, static_cast<double>(self.y()));
max_x = std::max(max_x, static_cast<double>(self.x()));
max_y = std::max(max_y, static_cast<double>(self.y()));
}
}
});
AND_THEN("Minimum X encountered is about 30.1") {
REQUIRE(min_x == Approx(30.1));
}
AND_THEN("Minimum Y encountered is about 30.1") {
REQUIRE(min_y == Approx(30.1));
}
AND_THEN("Maximum X encountered is about 49.9") {
REQUIRE(max_x == Approx(49.9));
}
AND_THEN("Maximum Y encountered is about 49.9") {
REQUIRE(max_y == Approx(49.9));
}
}
}
WHEN("--dont-arrange is supplied") {
in_args.emplace(in_args.cend()-1, "--dont-arrange");
cut.run(in_args.size(), to_cstr_array(in_args, args_cli));
THEN ("The first layer of the print should be centered around 0,0") {
auto reader {GCodeReader()};
std::string exported { read_to_string("test_cli/20mmbox.gcode"s)};
REQUIRE(exported != ""s);
double min_x = 50.0, max_x = -50.0, min_y = 50.0, max_y = -50.0;
reader.apply_config(cut.full_print_config());
reader.parse_buffer(exported, [&min_x, &min_y, &max_x, &max_y] (GCodeReader& self, const GCodeReader::GCodeLine& line)
{
if (self.z() < 0.6) {
min_x = std::min(min_x, static_cast<double>(self.x()));
min_y = std::min(min_y, static_cast<double>(self.y()));
max_x = std::max(max_x, static_cast<double>(self.x()));
max_y = std::max(max_y, static_cast<double>(self.y()));
}
});
AND_THEN("Minimum X encountered is about -9.9") {
REQUIRE(min_x == Approx(-9.9));
}
AND_THEN("Minimum Y encountered is about -9.9") {
REQUIRE(min_y == Approx(-9.9));
}
AND_THEN("Maximum X encountered is about 9.9") {
REQUIRE(max_x == Approx(9.9));
}
AND_THEN("Maximum Y encountered is about 9.9") {
REQUIRE(max_y == Approx(9.9));
}
}
}
clean_array(in_args.size(), args_cli);
clean_file("test_cli/20mmbox", "gcode");
}
}

View File

@ -0,0 +1,235 @@
#include <catch.hpp>
#ifndef WX_PRECOMP
#include "wx/app.h"
#include "wx/sizer.h"
#include "wx/checkbox.h"
#include "wx/uiaction.h"
#endif // WX_PRECOMP
#include <iostream>
#include "testableframe.h"
#include "OptionsGroup/Field.hpp"
#include "ConfigBase.hpp"
using namespace Slic3r::GUI;
SCENARIO( "GUI Checkbox option items fire their on_kill_focus when focus leaves the checkbox." ) {
wxTestableFrame* old = dynamic_cast<wxTestableFrame*>(wxTheApp->GetTopWindow());
old->Destroy();
wxTheApp->SetTopWindow(new wxTestableFrame());
wxUIActionSimulator sim;
wxMilliSleep(500);
GIVEN( "A checkbox field item exists on a window") {
auto exec_counter {0};
auto test_field {Slic3r::GUI::UI_Checkbox(wxTheApp->GetTopWindow(), Slic3r::ConfigOptionDef())};
auto killfunc {[&exec_counter](const std::string& opt_id) { exec_counter += 1; }};
test_field.on_kill_focus = killfunc;
wxTheApp->GetTopWindow()->Show();
wxTheApp->GetTopWindow()->Fit();
WHEN ( "focus leaves the checkbox") {
exec_counter = 0;
test_field.check()->SetFocus();
wxMilliSleep(500);
auto ev {wxFocusEvent(wxEVT_KILL_FOCUS, test_field.check()->GetId())};
ev.SetEventObject(test_field.check());
test_field.check()->ProcessWindowEvent(ev);
wxYield();
wxMilliSleep(500);
THEN( "on_focus_kill is executed.") {
REQUIRE(exec_counter == 1);
}
}
WHEN ( "focus leaves the checkbox and no callback is assigned") {
test_field.on_kill_focus = nullptr;
exec_counter = 0;
test_field.check()->SetFocus();
wxMilliSleep(500);
auto ev {wxFocusEvent(wxEVT_KILL_FOCUS, test_field.check()->GetId())};
ev.SetEventObject(test_field.check());
test_field.check()->ProcessWindowEvent(ev);
wxYield();
wxMilliSleep(500);
THEN( "on_focus_kill doesn't try to execute nullptr") {
REQUIRE(1 == 1);
REQUIRE(exec_counter == 0);
}
}
}
}
SCENARIO( "GUI Checkbox set_value and get_bool work as expected." ) {
wxTestableFrame* old = dynamic_cast<wxTestableFrame*>(wxTheApp->GetTopWindow());
old->Destroy();
wxTheApp->SetTopWindow(new wxTestableFrame());
wxMilliSleep(500);
auto test_field {Slic3r::GUI::UI_Checkbox(wxTheApp->GetTopWindow(), Slic3r::ConfigOptionDef())};
GIVEN( "A checkbox field item exists on a window") {
WHEN ( "set_value is an bool and true") {
test_field.set_value(true);
THEN( " Result is converted correctly.") {
REQUIRE( test_field.get_bool() == true);
}
}
WHEN ( "set_value is an bool and false") {
test_field.set_value(false);
THEN( " Result is converted correctly.") {
REQUIRE( test_field.get_bool() == false);
}
}
WHEN ( "set_value is a floating point number > 0") {
test_field.set_value(true);
try {
test_field.set_value(10.2);
} catch (boost::bad_any_cast &e) {
THEN( " Nothing happens; exception was thrown (and caught).") {
REQUIRE(true);
}
}
THEN( " Value did not change.") {
REQUIRE( test_field.get_bool() == true);
}
}
}
}
SCENARIO( "GUI Checkbox option respond to EVT_CHECKBOX when appropriate." ) {
wxUIActionSimulator sim;
wxTestableFrame* old = dynamic_cast<wxTestableFrame*>(wxTheApp->GetTopWindow());
old->Destroy();
wxTheApp->SetTopWindow(new wxTestableFrame());
auto* boolopt { new Slic3r::ConfigOptionBool(true) };
wxMilliSleep(500);
GIVEN( "A checkbox field item and disable_change = false") {
auto exec_counter {0};
auto changefunc {[&exec_counter](const std::string& opt_id, bool value) { exec_counter += 1; }};
auto test_field {Slic3r::GUI::UI_Checkbox(wxTheApp->GetTopWindow(), Slic3r::ConfigOptionDef())};
test_field.disable_change_event = false;
test_field.on_change = changefunc;
wxTheApp->GetTopWindow()->Show();
wxTheApp->GetTopWindow()->Fit();
WHEN ( "CHECKBOX event is received") {
exec_counter = 0;
auto ev {wxCommandEvent(wxEVT_CHECKBOX, test_field.check()->GetId())};
ev.SetEventObject(test_field.check());
test_field.check()->ProcessWindowEvent(ev);
wxYield();
wxMilliSleep(250);
THEN ( "on_change is executed.") {
REQUIRE(exec_counter == 1);
}
}
}
GIVEN( "A checkbox field item and disable_change = true") {
auto exec_counter {0};
auto changefunc {[&exec_counter] (const std::string& opt_id, bool value) { exec_counter++; }};
auto test_field {Slic3r::GUI::UI_Checkbox(wxTheApp->GetTopWindow(), Slic3r::ConfigOptionDef())};
wxTheApp->GetTopWindow()->Show();
wxTheApp->GetTopWindow()->Fit();
test_field.disable_change_event = true;
test_field.on_change = changefunc;
WHEN ( "CHECKBOX event is received") {
exec_counter = 0;
auto ev {wxCommandEvent(wxEVT_CHECKBOX, test_field.check()->GetId())};
ev.SetEventObject(test_field.check());
test_field.check()->ProcessWindowEvent(ev);
wxYield();
wxMilliSleep(250);
THEN ( "on_change is not executed.") {
REQUIRE(exec_counter == 0);
}
}
}
GIVEN( "A checkbox field item and readonly") {
struct Slic3r::ConfigOptionDef simple_option;
simple_option.default_value = boolopt;
simple_option.readonly = true;
auto exec_counter {0};
auto changefunc {[&exec_counter] (const std::string& opt_id, bool value) { exec_counter++; }};
auto* test_field {new Slic3r::GUI::UI_Checkbox(wxTheApp->GetTopWindow(), simple_option)};
wxTheApp->GetTopWindow()->Show();
wxTheApp->GetTopWindow()->Fit();
test_field->disable_change_event = false; // don't disable :D
test_field->on_change = changefunc;
WHEN ( "CHECKBOX event is received") {
exec_counter = 0;
test_field->set_value(false);
auto ev {wxCommandEvent(wxEVT_CHECKBOX, test_field->check()->GetId())};
ev.SetEventObject(test_field->check());
test_field->check()->ProcessWindowEvent(ev);
wxYield();
wxMilliSleep(250);
THEN ( "on_change is not executed.") {
REQUIRE(exec_counter == 0);
}
}
WHEN ( "the box is clicked and enabled") {
exec_counter = 0;
test_field->enable();
test_field->set_value(true);
auto ev {wxCommandEvent(wxEVT_CHECKBOX, test_field->check()->GetId())};
ev.SetEventObject(test_field->check());
test_field->check()->ProcessWindowEvent(ev);
wxYield();
wxMilliSleep(250);
THEN ( "on_change is executed.") {
REQUIRE(exec_counter == 1);
}
THEN ( "Box is checked.") {
REQUIRE(test_field->get_bool() == true);
}
}
WHEN ( "the box is clicked and toggled true") {
exec_counter = 0;
test_field->set_value(true);
test_field->toggle(true);
auto ev {wxCommandEvent(wxEVT_CHECKBOX, test_field->check()->GetId())};
ev.SetEventObject(test_field->check());
test_field->check()->ProcessWindowEvent(ev);
wxYield();
wxMilliSleep(250);
THEN ( "on_change is executed.") {
REQUIRE(exec_counter == 1);
}
THEN ( "Box is checked.") {
REQUIRE(test_field->get_bool() == true);
}
}
WHEN ( "the box is clicked and toggled false") {
exec_counter = 0;
test_field->set_value(true);
test_field->toggle(false);
auto ev {wxCommandEvent(wxEVT_CHECKBOX, test_field->check()->GetId())};
ev.SetEventObject(test_field->check());
test_field->check()->ProcessWindowEvent(ev);
wxYield();
wxMilliSleep(250);
THEN ( "on_change is not executed.") {
REQUIRE(exec_counter == 0);
}
THEN ( "Box is checked.") {
REQUIRE(test_field->get_bool() == true);
}
}
delete test_field;
}
}

View File

@ -0,0 +1,143 @@
#include <catch.hpp>
#ifndef WX_PRECOMP
#include "wx/app.h"
#include "wx/uiaction.h"
#endif // WX_PRECOMP
#include <iostream>
#include "testableframe.h"
#include "OptionsGroup/Field.hpp"
#include "ConfigBase.hpp"
using namespace std::string_literals;
SCENARIO( "UI_Choice: default values from options") {
wxTestableFrame* old = dynamic_cast<wxTestableFrame*>(wxTheApp->GetTopWindow());
old->Destroy();
wxTheApp->SetTopWindow(new wxTestableFrame());
wxUIActionSimulator sim;
wxMilliSleep(500);
GIVEN( "I have a UI Choice with 3 options from ConfigOptionDef and a default_value that is not in the enumeration.") {
auto simple_option {ConfigOptionDef()};
auto* default_string {new ConfigOptionString("A")};
simple_option.default_value = default_string; // owned by ConfigOptionDef
simple_option.enum_values.push_back("B");
simple_option.enum_values.push_back("C");
simple_option.enum_values.push_back("D");
auto test_field {Slic3r::GUI::UI_Choice(wxTheApp->GetTopWindow(), simple_option)};
WHEN( "I don't explicitly select any option in the drop-down") {
THEN( "get_value() returns the the value associuted with the listed default option in the related ConfigOptionDef") {
REQUIRE(simple_option.default_value->getString() == test_field.get_string());
}
}
WHEN( "I select the first option in the drop-down") {
test_field.choice()->SetSelection(0);
THEN( "get_value() returns the the value associuted with the first option in the related ConfigOptionDef") {
REQUIRE(simple_option.enum_values[0] == test_field.get_string());
}
}
WHEN( "I select the second option in the drop-down") {
test_field.choice()->SetSelection(1);
THEN( "get_value() returns the the value associuted with the second option in the related ConfigOptionDef") {
REQUIRE(simple_option.enum_values[1] == test_field.get_string());
}
}
WHEN( "I select the third option in the drop-down") {
test_field.choice()->SetSelection(2);
THEN( "get_value() returns the the value associuted with the third option in the related ConfigOptionDef") {
REQUIRE(simple_option.enum_values[2] == test_field.get_string());
}
}
}
GIVEN( "I have a UI Choice with 3 options from ConfigOptionDef and a default_value that is in the enumeration.") {
auto simple_option {ConfigOptionDef()};
auto* default_string {new ConfigOptionString("B"s)};
simple_option.default_value = default_string; // owned by ConfigOptionDef
simple_option.enum_values.push_back("B"s);
simple_option.enum_values.push_back("C"s);
simple_option.enum_values.push_back("D"s);
auto test_field {Slic3r::GUI::UI_Choice(wxTheApp->GetTopWindow(), simple_option)};
WHEN( "I don't explicitly select any option in the drop-down") {
THEN( "get_value() returns the first value in the related ConfigOptionDef") {
REQUIRE(simple_option.enum_values[0] == test_field.get_string());
REQUIRE(test_field.choice()->FindString(simple_option.enum_values[0]) == 0);
}
}
WHEN( "I set the string value to another item in the enumeration") {
test_field.combo()->SetValue("C"s);
THEN( "get_string() returns the matching item in ConfigOptionDef") {
REQUIRE(test_field.get_string() == "C"s);
REQUIRE(test_field.choice()->FindString(simple_option.enum_values[1]) == 1);
}
}
WHEN( "I set the string value to another item that is not in the enumeration") {
test_field.combo()->SetValue("F"s);
THEN( "get_string() returns the matching item in ConfigOptionDef") {
REQUIRE(test_field.get_string() == "F"s);
REQUIRE(test_field.choice()->GetSelection() == wxNOT_FOUND);
}
}
}
}
SCENARIO( "UI_Choice: event handling for on_change and on_kill_focus") {
auto event_count {0};
auto killfunc {[&event_count](const std::string& opt_id) { event_count += 1; }};
auto changefunc {[&event_count](const std::string& opt_id, std::string value) { event_count += 1; }};
GIVEN( "I have a UI Choice with 2 options from ConfigOptionDef, no default value, and an on_change handler and on_kill_focus handler.") {
auto simple_option {ConfigOptionDef()};
auto* default_string {new ConfigOptionString("B")};
simple_option.default_value = default_string; // owned by ConfigOptionDef
simple_option.enum_values.push_back("B");
simple_option.enum_values.push_back("C");
auto test_field {Slic3r::GUI::UI_Choice(wxTheApp->GetTopWindow(), simple_option)};
test_field.on_kill_focus = killfunc;
test_field.on_change = changefunc;
WHEN( "I receive a wxEVT_COMBOBOX event") {
event_count = 0;
wxMilliSleep(250);
auto ev {wxCommandEvent(wxEVT_COMBOBOX, test_field.choice()->GetId())};
ev.SetEventObject(test_field.choice());
test_field.choice()->ProcessWindowEvent(ev);
THEN( "on_change handler is executed.") {
REQUIRE(event_count == 1);
}
}
WHEN( "I receive a wxEVT_TEXT_ENTER event") {
event_count = 0;
wxMilliSleep(250);
auto ev {wxCommandEvent(wxEVT_TEXT_ENTER, test_field.choice()->GetId())};
ev.SetEventObject(test_field.choice());
test_field.choice()->ProcessWindowEvent(ev);
THEN( "on_change handler is executed.") {
REQUIRE(event_count == 1);
}
}
WHEN( "My control loses focus.") {
event_count = 0;
test_field.choice()->SetFocus();
wxMilliSleep(250);
auto ev {wxFocusEvent(wxEVT_KILL_FOCUS, test_field.choice()->GetId())};
ev.SetEventObject(test_field.choice());
test_field.choice()->ProcessWindowEvent(ev);
THEN( "on_change handler is executed and on_kill_focus handler is executed.") {
REQUIRE(event_count == 2);
}
}
}
}

View File

@ -0,0 +1,113 @@
#include <catch.hpp>
#ifndef WX_PRECOMP
#include <wx/app.h>
#include <wx/sizer.h>
#include <wx/uiaction.h>
#include <wx/colour.h>
#include <wx/clrpicker.h>
#endif // WX_PRECOMP
#include <iostream>
#include "testableframe.h"
#include "OptionsGroup/Field.hpp"
#include "ConfigBase.hpp"
using namespace std::string_literals;
SCENARIO("UI_Color: default values from options and basic accessor methods") {
wxTestableFrame* old = dynamic_cast<wxTestableFrame*>(wxTheApp->GetTopWindow());
old->Destroy();
wxTheApp->SetTopWindow(new wxTestableFrame());
wxUIActionSimulator sim;
wxMilliSleep(500);
auto simple_option {ConfigOptionDef()};
auto* default_color {new ConfigOptionString("#FFFF00")};
auto event_count {0};
auto changefunc {[&event_count] (const std::string& opt_id, const std::string& color) { event_count++; }};
GIVEN("A Color Picker") {
simple_option.default_value = default_color;
auto test_field {Slic3r::GUI::UI_Color(wxTheApp->GetTopWindow(), simple_option)};
test_field.on_change = changefunc;
WHEN("Object is constructed with default_value of '#FFFF00'.") {
THEN("get_string() returns '#FFFF00'") {
REQUIRE(test_field.get_string() == "#FFFF00"s);
}
THEN("get_int() returns 0") {
REQUIRE(test_field.get_int() == 0);
}
}
WHEN("Color picker receives a color picked event") {
event_count = 0;
test_field.disable_change_event = false;
auto ev {wxFocusEvent(wxEVT_COLOURPICKER_CHANGED, test_field.picker()->GetId())};
ev.SetEventObject(test_field.picker());
test_field.picker()->ProcessWindowEvent(ev);
THEN("_on_change fires.") {
REQUIRE(event_count == 1);
}
}
}
}
SCENARIO( "Color string value tests") {
wxTestableFrame* old = dynamic_cast<wxTestableFrame*>(wxTheApp->GetTopWindow());
old->Destroy();
wxTheApp->SetTopWindow(new wxTestableFrame());
wxUIActionSimulator sim;
wxMilliSleep(500);
auto simple_option {ConfigOptionDef()};
auto* default_color {new ConfigOptionString("#FFFFFF")};
GIVEN("A Color Picker") {
auto test_field {Slic3r::GUI::UI_Color(wxTheApp->GetTopWindow(), simple_option)};
WHEN("Set_value is called with a string of '#FFFFFF'") {
test_field.set_value("#FFFFFF");
THEN("Internal wxColor is equal to wxWhite") {
REQUIRE(test_field.picker()->GetColour() == wxColour(*wxWHITE));
}
}
WHEN("Set_value is called with a string of '#FFAACC'") {
test_field.set_value("#FFAACC");
THEN("Internal wxColor is equal to wxColor(255, 170, 204)") {
REQUIRE(test_field.picker()->GetColour() == wxColour(255, 170, 204));
}
}
WHEN("Set_value is called with a string of '#3020FF'") {
test_field.set_value("#3020FF");
THEN("Internal wxColor is equal to wxColor(48, 32, 255)") {
REQUIRE(test_field.picker()->GetColour() == wxColour(48,32,255));
}
}
WHEN("Set_value is called with a string of '#01A06D'") {
test_field.set_value("#01A06D");
THEN("Internal wxColor is equal to wxColor(01, 160, 109)") {
REQUIRE(test_field.picker()->GetColour() == wxColour(1,160,109));
}
}
WHEN("Internal color is set to wxWHITE") {
test_field.picker()->SetColour(wxColour(*wxWHITE));
THEN("String value is #FFFFFF") {
REQUIRE(test_field.get_string() == "#FFFFFF"s);
}
}
WHEN("Internal color is set to wxRED") {
test_field.picker()->SetColour(wxColour(*wxRED));
THEN("String value is #FF0000") {
REQUIRE(test_field.get_string() == "#FF0000"s);
}
}
WHEN("Internal color is set to wxGREEN") {
test_field.picker()->SetColour(wxColour(*wxGREEN));
THEN("String value is #00FF00") {
REQUIRE(test_field.get_string() == "#00FF00"s);
}
}
WHEN("Internal color is set to wxBLUE") {
test_field.picker()->SetColour(wxColour(*wxBLUE));
THEN("String value is #0000FF") {
REQUIRE(test_field.get_string() == "#0000FF"s);
}
}
}
delete default_color;
}

View File

@ -0,0 +1,312 @@
#include <catch.hpp>
#ifndef WX_PRECOMP
#include "wx/app.h"
#include "wx/uiaction.h"
#endif // WX_PRECOMP
#include <iostream>
#include "testableframe.h"
#include "OptionsGroup/Field.hpp"
#include "ConfigBase.hpp"
using namespace std::string_literals;
SCENARIO( "UI_NumChoice: default values from options") {
wxTestableFrame* old = dynamic_cast<wxTestableFrame*>(wxTheApp->GetTopWindow());
old->Destroy();
wxTheApp->SetTopWindow(new wxTestableFrame());
wxUIActionSimulator sim;
wxMilliSleep(500);
GIVEN( "I have a UI NumChoice with 3 options from ConfigOptionDef that has only values and a default_value that is not in the enumeration.") {
auto simple_option {ConfigOptionDef()};
auto* default_string {new ConfigOptionString("1")};
simple_option.default_value = default_string; // owned by ConfigOptionDef
simple_option.enum_values.push_back("2");
simple_option.enum_values.push_back("3");
simple_option.enum_values.push_back("4");
auto test_field {Slic3r::GUI::UI_NumChoice(wxTheApp->GetTopWindow(), simple_option)};
wxTheApp->GetTopWindow()->Show();
wxTheApp->GetTopWindow()->Fit();
WHEN( "I don't explicitly select any option in the drop-down") {
THEN( "get_value() returns the the value associated with the listed default option in the related ConfigOptionDef") {
REQUIRE(test_field.get_string() == simple_option.default_value->getString());
}
}
WHEN( "I select the first option in the drop-down") {
test_field.choice()->SetSelection(0);
THEN( "get_value() returns the the value associated with the first option in the related ConfigOptionDef") {
REQUIRE(test_field.get_string() == simple_option.enum_values[0]);
}
}
WHEN( "I select the second option in the drop-down") {
test_field.choice()->SetSelection(1);
THEN( "get_value() returns the the value associated with the second option in the related ConfigOptionDef") {
REQUIRE(test_field.get_string() == simple_option.enum_values[1]);
}
}
WHEN( "I select the third option in the drop-down") {
test_field.choice()->SetSelection(2);
THEN( "get_value() returns the the value associated with the third option in the related ConfigOptionDef") {
REQUIRE(test_field.get_string() == simple_option.enum_values[2]);
}
}
}
GIVEN( "I have a UI NumChoice with 3 options from ConfigOptionDef that has only values and a default_value that is in the enumeration.") {
auto simple_option {ConfigOptionDef()};
auto* default_string {new ConfigOptionString("2"s)};
simple_option.default_value = default_string; // owned by ConfigOptionDef
simple_option.enum_values.push_back("2");
simple_option.enum_values.push_back("3");
simple_option.enum_values.push_back("4");
auto test_field {Slic3r::GUI::UI_NumChoice(wxTheApp->GetTopWindow(), simple_option)};
wxTheApp->GetTopWindow()->Show();
wxTheApp->GetTopWindow()->Fit();
WHEN( "I don't explicitly select any option in the drop-down") {
THEN( "get_value() returns the first value in the related ConfigOptionDef") {
REQUIRE(simple_option.enum_values[0] == test_field.get_string());
REQUIRE(test_field.choice()->FindString(simple_option.enum_values[0]) == 0);
}
}
WHEN( "I set the string value to another item in the enumeration") {
test_field.choice()->SetValue("3"s);
THEN( "get_string() returns the matching item in ConfigOptionDef") {
REQUIRE(test_field.get_string() == "3"s);
}
THEN( "get_int() returns the matching item in ConfigOptionDef as an integer") {
REQUIRE(test_field.get_int() == 3);
}
THEN( "get_double() returns the matching item in ConfigOptionDef as a double") {
REQUIRE(test_field.get_double() == 3.0);
}
THEN( "Combobox string shows up as the first enumeration value.") {
REQUIRE(test_field.choice()->GetValue() == simple_option.enum_values[1]);
}
}
WHEN( "I set the string value to another item that is not in the enumeration") {
test_field.choice()->SetValue("7"s);
THEN( "get_string() returns the matching item in ConfigOptionDef") {
REQUIRE(test_field.get_string() == "7"s);
REQUIRE(test_field.get_int() == 7);
REQUIRE(test_field.get_double() == 7.0);
REQUIRE(test_field.choice()->GetSelection() == wxNOT_FOUND);
}
}
}
GIVEN( "I have a UI NumChoice with 3 options from ConfigOptionDef that has values that are doubles and labels and a default_value that is not in the enumeration.") {
auto simple_option {ConfigOptionDef()};
auto* default_int {new ConfigOptionFloat(1.0)};
simple_option.default_value = default_int; // owned by ConfigOptionDef
simple_option.enum_values.push_back("2.2");
simple_option.enum_values.push_back("3.3");
simple_option.enum_values.push_back("4.4");
simple_option.enum_labels.push_back("B");
simple_option.enum_labels.push_back("C");
simple_option.enum_labels.push_back("D");
auto test_field {Slic3r::GUI::UI_NumChoice(wxTheApp->GetTopWindow(), simple_option)};
wxTheApp->GetTopWindow()->Show();
wxTheApp->GetTopWindow()->Fit();
WHEN( "I don't explicitly select any option in the drop-down") {
THEN( "get_value() returns the the value associated with the listed default option in the related ConfigOptionDef") {
REQUIRE(simple_option.default_value->getString() == test_field.get_string());
}
}
WHEN( "I select the first option in the drop-down") {
test_field.choice()->SetSelection(0);
THEN( "get_value() returns the the value associated with the first option in the related ConfigOptionDef") {
REQUIRE(simple_option.enum_values[0] == test_field.get_string());
}
}
WHEN( "I select the second option in the drop-down") {
test_field.choice()->SetSelection(1);
THEN( "get_value() returns the the value associated with the second option in the related ConfigOptionDef") {
REQUIRE(simple_option.enum_values[1] == test_field.get_string());
}
}
WHEN( "I select the third option in the drop-down") {
test_field.choice()->SetSelection(2);
THEN( "get_value() returns the the value associated with the third option in the related ConfigOptionDef") {
REQUIRE(simple_option.enum_values[2] == test_field.get_string());
}
}
}
GIVEN( "I have a UI NumChoice with 3 options from ConfigOptionDef that has values and labels and a default_value that is not in the enumeration.") {
auto simple_option {ConfigOptionDef()};
auto* default_int {new ConfigOptionInt(1)};
simple_option.default_value = default_int; // owned by ConfigOptionDef
simple_option.enum_values.push_back("2");
simple_option.enum_values.push_back("3");
simple_option.enum_values.push_back("4");
simple_option.enum_labels.push_back("B");
simple_option.enum_labels.push_back("C");
simple_option.enum_labels.push_back("D");
auto test_field {Slic3r::GUI::UI_NumChoice(wxTheApp->GetTopWindow(), simple_option)};
wxTheApp->GetTopWindow()->Show();
wxTheApp->GetTopWindow()->Fit();
WHEN( "I don't explicitly select any option in the drop-down") {
THEN( "get_value() returns the the value associated with the listed default option in the related ConfigOptionDef") {
REQUIRE(simple_option.default_value->getString() == test_field.get_string());
}
}
WHEN( "I select the first option in the drop-down") {
test_field.choice()->SetSelection(0);
THEN( "get_value() returns the the value associated with the first option in the related ConfigOptionDef") {
REQUIRE(simple_option.enum_values[0] == test_field.get_string());
}
}
WHEN( "I select the second option in the drop-down") {
test_field.choice()->SetSelection(1);
THEN( "get_value() returns the the value associated with the second option in the related ConfigOptionDef") {
REQUIRE(simple_option.enum_values[1] == test_field.get_string());
}
}
WHEN( "I select the third option in the drop-down") {
test_field.choice()->SetSelection(2);
THEN( "get_value() returns the the value associated with the third option in the related ConfigOptionDef") {
REQUIRE(simple_option.enum_values[2] == test_field.get_string());
}
}
}
GIVEN( "I have a UI NumChoice with 3 options from ConfigOptionDef that has values and labels and a default_value that is in the enumeration.") {
auto simple_option {ConfigOptionDef()};
auto* default_string {new ConfigOptionString("2"s)};
simple_option.default_value = default_string; // owned by ConfigOptionDef
simple_option.enum_values.push_back("2");
simple_option.enum_values.push_back("3");
simple_option.enum_values.push_back("4");
simple_option.enum_labels.push_back("B");
simple_option.enum_labels.push_back("C");
simple_option.enum_labels.push_back("D");
auto test_field {Slic3r::GUI::UI_NumChoice(wxTheApp->GetTopWindow(), simple_option)};
wxTheApp->GetTopWindow()->Show();
wxTheApp->GetTopWindow()->Fit();
WHEN( "I don't explicitly select any option in the drop-down") {
THEN( "get_value functions return the first value in the related ConfigOptionDef") {
REQUIRE(test_field.get_string() == simple_option.enum_values[0]);
}
THEN( "get_int() returns the matching item in ConfigOptionDef as an integer") {
REQUIRE(test_field.get_int() == 2);
}
THEN( "get_double() returns the matching item in ConfigOptionDef as a double") {
REQUIRE(test_field.get_double() == 2.0);
}
THEN( "choice.FindString returns the label matching the item") {
REQUIRE(test_field.choice()->FindString(simple_option.enum_labels[0]) == 0);
}
}
WHEN( "I set the string value to another item in the enumeration") {
test_field.set_value(3);
THEN( "get_string() returns the matching item in ConfigOptionDef") {
REQUIRE(test_field.get_string() == "3"s);
}
THEN( "get_int() returns the matching item in ConfigOptionDef as an integer") {
REQUIRE(test_field.get_int() == 3);
}
THEN( "get_double() returns the matching item in ConfigOptionDef as a double") {
REQUIRE(test_field.get_double() == 3.0);
}
THEN( "choice.GetValue() returns the label.") {
REQUIRE(test_field.choice()->GetValue() == "C"s);
REQUIRE(test_field.choice()->FindString(simple_option.enum_labels[1]) == 1);
}
}
WHEN( "I set the string value to another item that is not in the enumeration") {
test_field.set_value(7);
THEN( "get_string() returns the entered value.") {
REQUIRE(test_field.get_string() == "7"s);
}
THEN( "get_int() returns the entered value as an integer.") {
REQUIRE(test_field.get_int() == 7);
}
THEN( "get_double() returns the entered value as a double.") {
REQUIRE(test_field.get_double() == 7.0);
}
THEN( "Underlying selection is wxNOT_FOUND") {
REQUIRE(test_field.choice()->GetSelection() == wxNOT_FOUND);
}
}
}
}
SCENARIO( "UI_NumChoice: event handling for on_change and on_kill_focus") {
auto event_count {0};
auto killfunc {[&event_count](const std::string& opt_id) { event_count += 1; }};
auto changefunc {[&event_count](const std::string& opt_id, std::string value) { event_count += 1; }};
GIVEN( "I have a UI NumChoice with 2 options from ConfigOptionDef, no default value, and an on_change handler and on_kill_focus handler.") {
auto simple_option {ConfigOptionDef()};
auto* default_string {new ConfigOptionString("2")};
simple_option.default_value = default_string; // owned by ConfigOptionDef
simple_option.enum_values.push_back("2");
simple_option.enum_values.push_back("3");
auto test_field {Slic3r::GUI::UI_NumChoice(wxTheApp->GetTopWindow(), simple_option)};
wxTheApp->GetTopWindow()->Show();
wxTheApp->GetTopWindow()->Fit();
test_field.on_kill_focus = killfunc;
test_field.on_change = changefunc;
WHEN( "I receive a wxEVT_COMBOBOX event") {
event_count = 0;
wxMilliSleep(250);
auto ev {wxCommandEvent(wxEVT_COMBOBOX, test_field.choice()->GetId())};
ev.SetEventObject(test_field.choice());
test_field.choice()->ProcessWindowEvent(ev);
THEN( "on_change handler is executed.") {
REQUIRE(event_count == 1);
}
}
WHEN( "I receive a wxEVT_TEXT_ENTER event") {
event_count = 0;
wxMilliSleep(250);
auto ev {wxCommandEvent(wxEVT_TEXT_ENTER, test_field.choice()->GetId())};
ev.SetEventObject(test_field.choice());
test_field.choice()->ProcessWindowEvent(ev);
THEN( "on_change handler is executed.") {
REQUIRE(event_count == 1);
}
}
WHEN( "My control loses focus.") {
event_count = 0;
test_field.choice()->SetFocus();
wxMilliSleep(250);
auto ev {wxFocusEvent(wxEVT_KILL_FOCUS, test_field.choice()->GetId())};
ev.SetEventObject(test_field.choice());
test_field.choice()->ProcessWindowEvent(ev);
THEN( "on_change handler is executed and on_kill_focus handler is executed.") {
REQUIRE(event_count == 2);
}
}
}
}

View File

@ -0,0 +1,299 @@
#include <catch.hpp>
#ifndef WX_PRECOMP
#include "wx/app.h"
#include "wx/sizer.h"
#include "wx/uiaction.h"
#endif // WX_PRECOMP
#include <iostream>
#include <tuple>
#include "testableframe.h"
#include "OptionsGroup/Field.hpp"
#include "ConfigBase.hpp"
#include "Point.hpp"
using namespace std::string_literals;
SCENARIO( "UI_Point: default values from options and basic accessor methods") {
wxTestableFrame* old = dynamic_cast<wxTestableFrame*>(wxTheApp->GetTopWindow());
old->Destroy();
wxTheApp->SetTopWindow(new wxTestableFrame());
wxUIActionSimulator sim;
wxMilliSleep(500);
GIVEN( "A UI point method and a X,Y coordinate (3.2, 10.2) as the default_value") {
auto simple_option {ConfigOptionDef()};
auto* default_point {new ConfigOptionPoint(Pointf(3.2, 10.2))};
simple_option.default_value = default_point;
auto test_field {Slic3r::GUI::UI_Point(wxTheApp->GetTopWindow(), simple_option)};
THEN( "get_string() returns '3.2;10.2'.") {
REQUIRE(test_field.get_string() == "3.2;10.2"s);
}
THEN( "get_point() yields a Pointf structure with x = 3.2, y = 10.2") {
REQUIRE(test_field.get_point().x == 3.2);
REQUIRE(test_field.get_point().y == 10.2);
}
}
GIVEN( "A UI point method and a tooltip in simple_option") {
auto simple_option {ConfigOptionDef()};
auto* default_point {new ConfigOptionPoint(Pointf(3.2, 10.2))};
simple_option.default_value = default_point;
auto test_field {Slic3r::GUI::UI_Point(wxTheApp->GetTopWindow(), simple_option)};
THEN( "Tooltip for both labels and textctrls matches simple_option") {
REQUIRE(test_field.ctrl_x()->GetToolTipText().ToStdString() == simple_option.tooltip );
REQUIRE(test_field.lbl_x()->GetToolTipText().ToStdString() == simple_option.tooltip );
REQUIRE(test_field.ctrl_y()->GetToolTipText().ToStdString() == simple_option.tooltip );
REQUIRE(test_field.lbl_y()->GetToolTipText().ToStdString() == simple_option.tooltip );
}
THEN( "get_point() yields a Pointf structure with x = 3.2, y = 10.2") {
REQUIRE(test_field.get_point().x == 3.2);
REQUIRE(test_field.get_point().y == 10.2);
}
}
}
SCENARIO( "UI_Point: set_value works with several types of inputs") {
wxTestableFrame* old = dynamic_cast<wxTestableFrame*>(wxTheApp->GetTopWindow());
old->Destroy();
wxTheApp->SetTopWindow(new wxTestableFrame());
wxUIActionSimulator sim;
wxMilliSleep(500);
GIVEN( "A UI point method with no default value.") {
auto simple_option {ConfigOptionDef()};
auto test_field {Slic3r::GUI::UI_Point(wxTheApp->GetTopWindow(), simple_option)};
WHEN( "set_value is called with a Pointf(19.0, 2.1)") {
test_field.set_value(Pointf(19.0, 2.1));
THEN( "get_point() returns a Pointf(19.0, 2.1)") {
REQUIRE(test_field.get_point() == Pointf(19.0, 2.1));
}
THEN( "get_string() returns '19.0;2.1'") {
REQUIRE(test_field.get_string() == "19.0;2.1"s);
}
THEN( "X TextCtrl contains X coordinate") {
REQUIRE(test_field.ctrl_x()->GetValue() == wxString("19.0"s));
}
THEN( "Y TextCtrl contains Y coordinate") {
REQUIRE(test_field.ctrl_y()->GetValue() == wxString("2.1"s));
}
}
WHEN( "set_value is called with a Pointf3(19.0, 2.1, 0.2)") {
test_field.set_value(Pointf3(19.0, 2.1, 0.2));
THEN( "get_point() returns a Pointf(19.0, 2.1)") {
REQUIRE(test_field.get_point() == Pointf(19.0, 2.1));
}
THEN( "get_point3() returns a Pointf3(19.0, 2.1, 0.0)") {
REQUIRE(test_field.get_point3() == Pointf3(19.0, 2.1, 0.0));
}
THEN( "get_string() returns '19.0;2.1'") {
REQUIRE(test_field.get_string() == "19.0;2.1"s);
}
THEN( "X TextCtrl contains X coordinate") {
REQUIRE(test_field.ctrl_x()->GetValue() == wxString("19.0"s));
}
THEN( "Y TextCtrl contains Y coordinate") {
REQUIRE(test_field.ctrl_y()->GetValue() == wxString("2.1"s));
}
}
WHEN( "set_value is called with a string of the form '30.9;211.2'") {
test_field.set_value("30.9;211.2"s);
THEN( "get_point() returns a Pointf(30.9, 211.2)") {
REQUIRE(test_field.get_point() == Pointf(30.9, 211.2));
}
THEN( "get_string() returns '30.9;211.2'") {
REQUIRE(test_field.get_string() == "30.9;211.2"s);
}
THEN( "X TextCtrl contains X coordinate") {
REQUIRE(test_field.ctrl_x()->GetValue() == wxString("30.9"s));
}
THEN( "Y TextCtrl contains Y coordinate") {
REQUIRE(test_field.ctrl_y()->GetValue() == wxString("211.2"s));
}
}
WHEN( "set_value is called with a wxString of the form '30.9;211.2'") {
test_field.set_value(wxString("30.9;211.2"s));
THEN( "get_point() returns a Pointf(30.9, 211.2)") {
REQUIRE(test_field.get_point() == Pointf(30.9, 211.2));
}
THEN( "get_string() returns '30.9;211.2'") {
REQUIRE(test_field.get_string() == "30.9;211.2"s);
}
THEN( "X TextCtrl contains X coordinate") {
REQUIRE(test_field.ctrl_x()->GetValue() == wxString("30.9"s));
}
THEN( "Y TextCtrl contains Y coordinate") {
REQUIRE(test_field.ctrl_y()->GetValue() == wxString("211.2"s));
}
}
}
}
SCENARIO( "UI_Point: Event responses") {
wxTestableFrame* old = dynamic_cast<wxTestableFrame*>(wxTheApp->GetTopWindow());
old->Destroy();
wxTheApp->SetTopWindow(new wxTestableFrame());
wxMilliSleep(250);
GIVEN ( "A UI_Point with no default value and a registered on_change method that increments a counter.") {
auto simple_option {ConfigOptionDef()};
auto test_field {Slic3r::GUI::UI_Point(wxTheApp->GetTopWindow(), simple_option)};
auto event_count {0};
auto changefunc {[&event_count] (const std::string& opt_id, std::tuple<std::string, std::string> value) { event_count++; }};
auto killfunc {[&event_count](const std::string& opt_id) { event_count++; }};
test_field.on_change = changefunc;
test_field.on_kill_focus = killfunc;
test_field.disable_change_event = false;
WHEN( "kill focus event is received on X") {
event_count = 0;
auto ev {wxFocusEvent(wxEVT_KILL_FOCUS, test_field.ctrl_x()->GetId())};
ev.SetEventObject(test_field.ctrl_x());
test_field.ctrl_x()->ProcessWindowEvent(ev);
THEN( "on_kill_focus is executed.") {
REQUIRE(event_count == 2);
}
}
WHEN( "kill focus event is received on Y") {
event_count = 0;
auto ev {wxFocusEvent(wxEVT_KILL_FOCUS, test_field.ctrl_y()->GetId())};
ev.SetEventObject(test_field.ctrl_y());
test_field.ctrl_y()->ProcessWindowEvent(ev);
THEN( "on_kill_focus is executed.") {
REQUIRE(event_count == 2);
}
}
WHEN( "enter key pressed event is received on X") {
event_count = 0;
auto ev {wxCommandEvent(wxEVT_TEXT_ENTER, test_field.ctrl_x()->GetId())};
ev.SetEventObject(test_field.ctrl_x());
test_field.ctrl_x()->ProcessWindowEvent(ev);
THEN( "on_change is executed.") {
REQUIRE(event_count == 1);
}
}
WHEN( "enter key pressed event is received on Y") {
event_count = 0;
auto ev {wxCommandEvent(wxEVT_TEXT_ENTER, test_field.ctrl_y()->GetId())};
ev.SetEventObject(test_field.ctrl_y());
test_field.ctrl_y()->ProcessWindowEvent(ev);
THEN( "on_change is executed.") {
REQUIRE(event_count == 1);
}
}
}
GIVEN ( "A UI_Point with no default value and a registered on_change method that increments a counter only when disable_change_event = false.") {
auto simple_option {ConfigOptionDef()};
auto test_field {Slic3r::GUI::UI_Point(wxTheApp->GetTopWindow(), simple_option)};
auto event_count {0};
auto changefunc {[&event_count] (const std::string& opt_id, std::tuple<std::string, std::string> value) { event_count++; }};
auto killfunc {[&event_count](const std::string& opt_id) { event_count += 1; }};
test_field.on_change = changefunc;
test_field.on_kill_focus = killfunc;
test_field.disable_change_event = true;
WHEN( "kill focus event is received on X") {
event_count = 0;
auto ev {wxFocusEvent(wxEVT_KILL_FOCUS, test_field.ctrl_x()->GetId())};
ev.SetEventObject(test_field.ctrl_x());
test_field.ctrl_x()->ProcessWindowEvent(ev);
THEN( "on_kill_focus is executed.") {
REQUIRE(event_count == 1);
}
}
WHEN( "kill focus event is received on Y") {
event_count = 0;
auto ev {wxFocusEvent(wxEVT_KILL_FOCUS, test_field.ctrl_y()->GetId())};
ev.SetEventObject(test_field.ctrl_y());
test_field.ctrl_y()->ProcessWindowEvent(ev);
THEN( "on_kill_focus is executed.") {
REQUIRE(event_count == 1);
}
}
WHEN( "enter key pressed event is received on X") {
event_count = 0;
auto ev {wxCommandEvent(wxEVT_TEXT_ENTER, test_field.ctrl_x()->GetId())};
ev.SetEventObject(test_field.ctrl_x());
test_field.ctrl_x()->ProcessWindowEvent(ev);
THEN( "on_change is not executed.") {
REQUIRE(event_count == 0);
}
}
WHEN( "enter key pressed event is received on Y") {
event_count = 0;
auto ev {wxCommandEvent(wxEVT_TEXT_ENTER, test_field.ctrl_y()->GetId())};
ev.SetEventObject(test_field.ctrl_y());
test_field.ctrl_y()->ProcessWindowEvent(ev);
THEN( "on_change is not executed.") {
REQUIRE(event_count == 0);
}
}
}
}
SCENARIO( "UI_Point: Enable/Disable") {
wxTestableFrame* old = dynamic_cast<wxTestableFrame*>(wxTheApp->GetTopWindow());
old->Destroy();
wxTheApp->SetTopWindow(new wxTestableFrame());
wxMilliSleep(250);
GIVEN ( "A UI_Point with no default value.") {
auto simple_option {ConfigOptionDef()};
auto test_field {Slic3r::GUI::UI_Point(wxTheApp->GetTopWindow(), simple_option)};
WHEN( "disable() is called") {
test_field.disable();
THEN( "IsEnabled == False for X and Y textctrls") {
REQUIRE(test_field.ctrl_x()->IsEnabled() == false);
REQUIRE(test_field.ctrl_y()->IsEnabled() == false);
}
}
WHEN( "enable() is called") {
test_field.enable();
THEN( "IsEnabled == True for X and Y textctrls") {
REQUIRE(test_field.ctrl_x()->IsEnabled() == true);
REQUIRE(test_field.ctrl_y()->IsEnabled() == true);
}
}
WHEN( "toggle() is called with false argument") {
test_field.toggle(false);
THEN( "IsEnabled == False for X and Y textctrls") {
REQUIRE(test_field.ctrl_x()->IsEnabled() == false);
REQUIRE(test_field.ctrl_y()->IsEnabled() == false);
}
}
WHEN( "toggle() is called with true argument") {
test_field.toggle(true);
THEN( "IsEnabled == True for X and Y textctrls") {
REQUIRE(test_field.ctrl_x()->IsEnabled() == true);
REQUIRE(test_field.ctrl_y()->IsEnabled() == true);
}
}
}
}
SCENARIO( "UI_Point: get_sizer()") {
wxTestableFrame* old = dynamic_cast<wxTestableFrame*>(wxTheApp->GetTopWindow());
old->Destroy();
wxTheApp->SetTopWindow(new wxTestableFrame());
wxMilliSleep(250);
GIVEN ( "A UI_Point with no default value.") {
auto simple_option {ConfigOptionDef()};
auto test_field {Slic3r::GUI::UI_Point(wxTheApp->GetTopWindow(), simple_option)};
WHEN( "get_sizer() is called") {
THEN( "get_sizer() returns a wxSizer that has 4 direct children in it that are Windows.") {
REQUIRE(test_field.get_sizer()->GetItemCount() == 4);
auto tmp {test_field.get_sizer()->GetChildren().begin()};
REQUIRE((*tmp)->IsWindow() == true);
tmp++;
REQUIRE((*tmp)->IsWindow() == true);
tmp++;
REQUIRE((*tmp)->IsWindow() == true);
tmp++;
REQUIRE((*tmp)->IsWindow() == true);
}
}
}
}

View File

@ -0,0 +1,376 @@
#include <catch.hpp>
#ifndef WX_PRECOMP
#include "wx/app.h"
#include "wx/sizer.h"
#include "wx/uiaction.h"
#endif // WX_PRECOMP
#include <iostream>
#include <tuple>
#include "testableframe.h"
#include "OptionsGroup/Field.hpp"
#include "ConfigBase.hpp"
#include "Point.hpp"
using namespace std::string_literals;
SCENARIO( "UI_Point3: default values from options and basic accessor methods") {
wxTestableFrame* old = dynamic_cast<wxTestableFrame*>(wxTheApp->GetTopWindow());
old->Destroy();
wxTheApp->SetTopWindow(new wxTestableFrame());
wxUIActionSimulator sim;
wxMilliSleep(500);
GIVEN( "A UI point method and a X,Y coordinate (3.2, 10.2, 0.0) as the default_value") {
auto simple_option {ConfigOptionDef()};
auto* default_point {new ConfigOptionPoint3(Pointf3(3.2, 10.2, 0.0))};
simple_option.default_value = default_point;
auto test_field {Slic3r::GUI::UI_Point3(wxTheApp->GetTopWindow(), simple_option)};
THEN( "get_string() returns '3.2;10.2;0.0'.") {
REQUIRE(test_field.get_string() == "3.2;10.2;0.0"s);
}
THEN( "get_point() yields a Pointf structure with x = 3.2, y = 10.2") {
REQUIRE(test_field.get_point().x == 3.2);
REQUIRE(test_field.get_point().y == 10.2);
}
THEN( "get_point3() yields a Pointf3 structure with x = 3.2, y = 10.2, z = 0.0") {
REQUIRE(test_field.get_point3().x == 3.2);
REQUIRE(test_field.get_point3().y == 10.2);
REQUIRE(test_field.get_point3().z == 0.0);
}
}
GIVEN( "A UI point, coordinate in X/Y/Z and a tooltip in simple_option") {
auto simple_option {ConfigOptionDef()};
auto* default_point {new ConfigOptionPoint3(Pointf3(3.2, 10.2, 0.2))};
simple_option.default_value = default_point;
auto test_field {Slic3r::GUI::UI_Point3(wxTheApp->GetTopWindow(), simple_option)};
THEN( "Tooltip for both labels and textctrls matches simple_option") {
REQUIRE(test_field.ctrl_x()->GetToolTipText().ToStdString() == simple_option.tooltip );
REQUIRE(test_field.lbl_x()->GetToolTipText().ToStdString() == simple_option.tooltip );
REQUIRE(test_field.ctrl_y()->GetToolTipText().ToStdString() == simple_option.tooltip );
REQUIRE(test_field.lbl_y()->GetToolTipText().ToStdString() == simple_option.tooltip );
REQUIRE(test_field.ctrl_z()->GetToolTipText().ToStdString() == simple_option.tooltip );
REQUIRE(test_field.lbl_z()->GetToolTipText().ToStdString() == simple_option.tooltip );
}
THEN( "get_point3() yields a Pointf3 structure with x = 3.2, y = 10.2, z = 0.2") {
REQUIRE(test_field.get_point3().x == 3.2);
REQUIRE(test_field.get_point3().y == 10.2);
REQUIRE(test_field.get_point3().z == 0.2);
}
THEN( "get_point() yields a Pointf structure with x = 3.2, y = 10.2") {
REQUIRE(test_field.get_point().x == 3.2);
REQUIRE(test_field.get_point().y == 10.2);
}
}
}
SCENARIO( "UI_Point3: set_value works with several types of inputs") {
wxTestableFrame* old = dynamic_cast<wxTestableFrame*>(wxTheApp->GetTopWindow());
old->Destroy();
wxTheApp->SetTopWindow(new wxTestableFrame());
wxUIActionSimulator sim;
wxMilliSleep(500);
GIVEN( "A UI point method with no default value.") {
auto simple_option {ConfigOptionDef()};
auto test_field {Slic3r::GUI::UI_Point3(wxTheApp->GetTopWindow(), simple_option)};
WHEN( "set_value is called with a Pointf(19.0, 2.1)") {
test_field.set_value(Pointf(19.0, 2.1));
THEN( "get_point3() returns a Pointf3(19.0, 2.1, 0.0)") {
REQUIRE(test_field.get_point3() == Pointf3(19.0, 2.1, 0.0));
}
THEN( "get_point() returns a Pointf(19.0, 2.1)") {
REQUIRE(test_field.get_point() == Pointf(19.0, 2.1));
}
THEN( "get_string() returns '19.0;2.1'") {
REQUIRE(test_field.get_string() == "19.0;2.1;0.0"s);
}
THEN( "X TextCtrl contains X coordinate") {
REQUIRE(test_field.ctrl_x()->GetValue() == wxString("19.0"s));
}
THEN( "Y TextCtrl contains Y coordinate") {
REQUIRE(test_field.ctrl_y()->GetValue() == wxString("2.1"s));
}
THEN( "Z TextCtrl contains Z coordinate") {
REQUIRE(test_field.ctrl_z()->GetValue() == wxString("0.0"s));
}
}
WHEN( "set_value is called with a Pointf3(19.0, 2.1, 0.6)") {
test_field.set_value(Pointf3(19.0, 2.1, 0.6));
THEN( "get_point3() returns a Pointf3(19.0, 2.1, 0.6)") {
REQUIRE(test_field.get_point3() == Pointf3(19.0, 2.1, 0.6));
}
THEN( "get_point() returns a Pointf(19.0, 2.1)") {
REQUIRE(test_field.get_point() == Pointf(19.0, 2.1));
}
THEN( "get_string() returns '19.0;2.1;0.6'") {
REQUIRE(test_field.get_string() == "19.0;2.1;0.6"s);
}
THEN( "X TextCtrl contains X coordinate") {
REQUIRE(test_field.ctrl_x()->GetValue() == wxString("19.0"s));
}
THEN( "Y TextCtrl contains Y coordinate") {
REQUIRE(test_field.ctrl_y()->GetValue() == wxString("2.1"s));
}
THEN( "Z TextCtrl contains Z coordinate") {
REQUIRE(test_field.ctrl_z()->GetValue() == wxString("0.6"s));
}
}
WHEN( "set_value is called with a string of the form '30.9;211.2;411.0'") {
test_field.set_value("30.9;211.2;411.0"s);
THEN( "get_point3() returns a Pointf3(30.9, 211.2, 411.0)") {
REQUIRE(test_field.get_point3() == Pointf3(30.9, 211.2, 411.0));
}
THEN( "get_point() returns a Pointf(30.9, 211.2)") {
REQUIRE(test_field.get_point() == Pointf(30.9, 211.2));
}
THEN( "get_string() returns '30.9;211.2;411.0'") {
REQUIRE(test_field.get_string() == "30.9;211.2;411.0"s);
}
THEN( "X TextCtrl contains X coordinate") {
REQUIRE(test_field.ctrl_x()->GetValue() == wxString("30.9"s));
}
THEN( "Y TextCtrl contains Y coordinate") {
REQUIRE(test_field.ctrl_y()->GetValue() == wxString("211.2"s));
}
THEN( "Z TextCtrl contains Z coordinate") {
REQUIRE(test_field.ctrl_z()->GetValue() == wxString("411.0"s));
}
}
WHEN( "set_value is called with a wxString of the form '30.9;211.2;411.0'") {
test_field.set_value(wxString("30.9;211.2;411.0"s));
THEN( "get_point3() returns a Pointf3(30.9, 211.2, 411.0)") {
REQUIRE(test_field.get_point3() == Pointf3(30.9, 211.2, 411.0));
}
THEN( "get_point() returns a Pointf(30.9, 211.2)") {
REQUIRE(test_field.get_point() == Pointf(30.9, 211.2));
}
THEN( "get_string() returns '30.9;211.2;411.0'") {
REQUIRE(test_field.get_string() == "30.9;211.2;411.0"s);
}
THEN( "X TextCtrl contains X coordinate") {
REQUIRE(test_field.ctrl_x()->GetValue() == wxString("30.9"s));
}
THEN( "Y TextCtrl contains Y coordinate") {
REQUIRE(test_field.ctrl_y()->GetValue() == wxString("211.2"s));
}
THEN( "Z TextCtrl contains Z coordinate") {
REQUIRE(test_field.ctrl_z()->GetValue() == wxString("411.0"s));
}
}
}
}
SCENARIO( "UI_Point3: Event responses") {
wxTestableFrame* old = dynamic_cast<wxTestableFrame*>(wxTheApp->GetTopWindow());
old->Destroy();
wxTheApp->SetTopWindow(new wxTestableFrame());
wxMilliSleep(250);
GIVEN ( "A UI_Point3 with no default value and a registered on_change method that increments a counter.") {
auto simple_option {ConfigOptionDef()};
auto test_field {Slic3r::GUI::UI_Point3(wxTheApp->GetTopWindow(), simple_option)};
auto event_count {0};
auto changefunc {[&event_count] (const std::string& opt_id, std::tuple<std::string, std::string, std::string> value) { event_count++; }};
auto killfunc {[&event_count](const std::string& opt_id) { event_count++; }};
test_field.on_change = changefunc;
test_field.on_kill_focus = killfunc;
test_field.disable_change_event = false;
WHEN( "kill focus event is received on X") {
event_count = 0;
auto ev {wxFocusEvent(wxEVT_KILL_FOCUS, test_field.ctrl_x()->GetId())};
ev.SetEventObject(test_field.ctrl_x());
test_field.ctrl_x()->ProcessWindowEvent(ev);
THEN( "on_kill_focus is executed.") {
REQUIRE(event_count == 2);
}
}
WHEN( "kill focus event is received on Y") {
event_count = 0;
auto ev {wxFocusEvent(wxEVT_KILL_FOCUS, test_field.ctrl_y()->GetId())};
ev.SetEventObject(test_field.ctrl_y());
test_field.ctrl_y()->ProcessWindowEvent(ev);
THEN( "on_kill_focus is executed.") {
REQUIRE(event_count == 2);
}
}
WHEN( "kill focus event is received on Z") {
event_count = 0;
auto ev {wxFocusEvent(wxEVT_KILL_FOCUS, test_field.ctrl_z()->GetId())};
ev.SetEventObject(test_field.ctrl_z());
test_field.ctrl_z()->ProcessWindowEvent(ev);
THEN( "on_kill_focus is executed.") {
REQUIRE(event_count == 2);
}
}
WHEN( "enter key pressed event is received on X") {
event_count = 0;
auto ev {wxCommandEvent(wxEVT_TEXT_ENTER, test_field.ctrl_x()->GetId())};
ev.SetEventObject(test_field.ctrl_x());
test_field.ctrl_x()->ProcessWindowEvent(ev);
THEN( "on_change is executed.") {
REQUIRE(event_count == 1);
}
}
WHEN( "enter key pressed event is received on Y") {
event_count = 0;
auto ev {wxCommandEvent(wxEVT_TEXT_ENTER, test_field.ctrl_y()->GetId())};
ev.SetEventObject(test_field.ctrl_y());
test_field.ctrl_y()->ProcessWindowEvent(ev);
THEN( "on_change is executed.") {
REQUIRE(event_count == 1);
}
}
WHEN( "enter key pressed event is received on Z") {
event_count = 0;
auto ev {wxCommandEvent(wxEVT_TEXT_ENTER, test_field.ctrl_z()->GetId())};
ev.SetEventObject(test_field.ctrl_z());
test_field.ctrl_z()->ProcessWindowEvent(ev);
THEN( "on_change is executed.") {
REQUIRE(event_count == 1);
}
}
}
GIVEN ( "A UI_Point3 with no default value and a registered on_change method that increments a counter only when disable_change_event = false.") {
auto simple_option {ConfigOptionDef()};
auto test_field {Slic3r::GUI::UI_Point3(wxTheApp->GetTopWindow(), simple_option)};
auto event_count {0};
auto changefunc {[&event_count] (const std::string& opt_id, std::tuple<std::string, std::string, std::string> value) { event_count++; }};
auto killfunc {[&event_count](const std::string& opt_id) { event_count += 1; }};
test_field.on_change = changefunc;
test_field.on_kill_focus = killfunc;
test_field.disable_change_event = true;
WHEN( "kill focus event is received on X") {
event_count = 0;
auto ev {wxFocusEvent(wxEVT_KILL_FOCUS, test_field.ctrl_x()->GetId())};
ev.SetEventObject(test_field.ctrl_x());
test_field.ctrl_x()->ProcessWindowEvent(ev);
THEN( "on_kill_focus is executed.") {
REQUIRE(event_count == 1);
}
}
WHEN( "kill focus event is received on Y") {
event_count = 0;
auto ev {wxFocusEvent(wxEVT_KILL_FOCUS, test_field.ctrl_y()->GetId())};
ev.SetEventObject(test_field.ctrl_y());
test_field.ctrl_y()->ProcessWindowEvent(ev);
THEN( "on_kill_focus is executed.") {
REQUIRE(event_count == 1);
}
}
WHEN( "kill focus event is received on Z") {
event_count = 0;
auto ev {wxFocusEvent(wxEVT_KILL_FOCUS, test_field.ctrl_z()->GetId())};
ev.SetEventObject(test_field.ctrl_z());
test_field.ctrl_z()->ProcessWindowEvent(ev);
THEN( "on_kill_focus is executed.") {
REQUIRE(event_count == 1);
}
}
WHEN( "enter key pressed event is received on X") {
event_count = 0;
auto ev {wxCommandEvent(wxEVT_TEXT_ENTER, test_field.ctrl_x()->GetId())};
ev.SetEventObject(test_field.ctrl_x());
test_field.ctrl_x()->ProcessWindowEvent(ev);
THEN( "on_change is not executed.") {
REQUIRE(event_count == 0);
}
}
WHEN( "enter key pressed event is received on Y") {
event_count = 0;
auto ev {wxCommandEvent(wxEVT_TEXT_ENTER, test_field.ctrl_y()->GetId())};
ev.SetEventObject(test_field.ctrl_y());
test_field.ctrl_y()->ProcessWindowEvent(ev);
THEN( "on_change is not executed.") {
REQUIRE(event_count == 0);
}
}
WHEN( "enter key pressed event is received on Z") {
event_count = 0;
auto ev {wxCommandEvent(wxEVT_TEXT_ENTER, test_field.ctrl_z()->GetId())};
ev.SetEventObject(test_field.ctrl_z());
test_field.ctrl_z()->ProcessWindowEvent(ev);
THEN( "on_change is not executed.") {
REQUIRE(event_count == 0);
}
}
}
}
SCENARIO( "UI_Point3: Enable/Disable") {
wxTestableFrame* old = dynamic_cast<wxTestableFrame*>(wxTheApp->GetTopWindow());
old->Destroy();
wxTheApp->SetTopWindow(new wxTestableFrame());
wxMilliSleep(250);
GIVEN ( "A UI_Point3 with no default value.") {
auto simple_option {ConfigOptionDef()};
auto test_field {Slic3r::GUI::UI_Point3(wxTheApp->GetTopWindow(), simple_option)};
WHEN( "disable() is called") {
test_field.disable();
THEN( "IsEnabled == False for X and Y and Z textctrls") {
REQUIRE(test_field.ctrl_x()->IsEnabled() == false);
REQUIRE(test_field.ctrl_y()->IsEnabled() == false);
REQUIRE(test_field.ctrl_z()->IsEnabled() == false);
}
}
WHEN( "enable() is called") {
test_field.enable();
THEN( "IsEnabled == True for X and Y and Z textctrls") {
REQUIRE(test_field.ctrl_x()->IsEnabled() == true);
REQUIRE(test_field.ctrl_y()->IsEnabled() == true);
REQUIRE(test_field.ctrl_z()->IsEnabled() == true);
}
}
WHEN( "toggle() is called with false argument") {
test_field.toggle(false);
THEN( "IsEnabled == False for X and Y and Z textctrls") {
REQUIRE(test_field.ctrl_x()->IsEnabled() == false);
REQUIRE(test_field.ctrl_y()->IsEnabled() == false);
REQUIRE(test_field.ctrl_z()->IsEnabled() == false);
}
}
WHEN( "toggle() is called with true argument") {
test_field.toggle(true);
THEN( "IsEnabled == True for X and Y and Z textctrls") {
REQUIRE(test_field.ctrl_x()->IsEnabled() == true);
REQUIRE(test_field.ctrl_y()->IsEnabled() == true);
REQUIRE(test_field.ctrl_z()->IsEnabled() == true);
}
}
}
}
SCENARIO( "UI_Point3: get_sizer()") {
wxTestableFrame* old = dynamic_cast<wxTestableFrame*>(wxTheApp->GetTopWindow());
old->Destroy();
wxTheApp->SetTopWindow(new wxTestableFrame());
wxMilliSleep(250);
GIVEN ( "A UI_Point3 with no default value.") {
auto simple_option {ConfigOptionDef()};
auto test_field {Slic3r::GUI::UI_Point3(wxTheApp->GetTopWindow(), simple_option)};
WHEN( "get_sizer() is called") {
THEN( "get_sizer() returns a wxSizer that has 6 direct children in it that are Windows.") {
REQUIRE(test_field.get_sizer()->GetItemCount() == 6);
auto tmp {test_field.get_sizer()->GetChildren().begin()};
REQUIRE((*tmp)->IsWindow() == true);
tmp++;
REQUIRE((*tmp)->IsWindow() == true);
tmp++;
REQUIRE((*tmp)->IsWindow() == true);
tmp++;
REQUIRE((*tmp)->IsWindow() == true);
tmp++;
REQUIRE((*tmp)->IsWindow() == true);
tmp++;
REQUIRE((*tmp)->IsWindow() == true);
}
}
}
}

View File

@ -0,0 +1,207 @@
#include <catch.hpp>
#ifndef WX_PRECOMP
#include <wx/app.h>
#include <wx/sizer.h>
#include <wx/uiaction.h>
#include <wx/slider.h>
#endif // WX_PRECOMP
#include "testableframe.h"
#include "OptionsGroup/Field.hpp"
#include "ConfigBase.hpp"
using namespace std::string_literals;
SCENARIO( "UI_Slider: Defaults, Min/max handling, accessors.") {
wxTestableFrame* old = dynamic_cast<wxTestableFrame*>(wxTheApp->GetTopWindow());
old->Destroy();
wxTheApp->SetTopWindow(new wxTestableFrame());
wxUIActionSimulator sim;
wxMilliSleep(500);
auto simple_option {ConfigOptionDef()};
auto* default_color {new ConfigOptionFloat(30.0)};
simple_option.min = 0;
simple_option.max = 60;
simple_option.default_value = default_color;
GIVEN("A UI Slider with default scale") {
auto event_count {0};
auto changefunc {[&event_count] (const std::string& opt_id, const double& color) { event_count++; }};
WHEN("Option min is 0") {
simple_option.default_value = default_color;
auto test_field {Slic3r::GUI::UI_Slider(wxTheApp->GetTopWindow(), simple_option)};
THEN("default of 0 is used.") {
REQUIRE(test_field.slider()->GetMin() == 0);
}
}
WHEN("Option max is 0") {
simple_option.max = 0;
simple_option.default_value = default_color;
auto test_field {Slic3r::GUI::UI_Slider(wxTheApp->GetTopWindow(), simple_option)};
THEN("default of 100*10(scale) is used.") {
REQUIRE(test_field.slider()->GetMax() == 1000);
}
}
WHEN("Default value is used.") {
simple_option.default_value = default_color;
auto test_field {Slic3r::GUI::UI_Slider(wxTheApp->GetTopWindow(), simple_option)};
THEN("Raw slider value is 30 * scale (10) = 300") {
REQUIRE(test_field.slider()->GetValue() == 300);
}
}
WHEN("set_scale is called with 25 for argument") {
simple_option.default_value = default_color;
simple_option.max = 100;
auto test_field {Slic3r::GUI::UI_Slider(wxTheApp->GetTopWindow(), simple_option)};
test_field.set_scale(25);
THEN("Slider min is 0") {
REQUIRE(test_field.slider()->GetMin() == 0);
}
THEN("Slider max is 100*25 = 2500") {
REQUIRE(test_field.slider()->GetMax() == 2500);
}
THEN("Slider raw value is 30*25 = 750") {
REQUIRE(test_field.slider()->GetValue() == 750);
}
THEN("UI_Slider get_double still reads 30") {
REQUIRE(test_field.get_double() == Approx(30));
}
THEN("UI_Slider get_int reads 30") {
REQUIRE(test_field.get_int() == 30);
}
THEN("Textctrl raw value still reads 30") {
REQUIRE(test_field.textctrl()->GetValue() == "30.0"s);
}
test_field.on_change = changefunc;
THEN("on_change does not fire.") {
REQUIRE(event_count == 0);
}
}
}
GIVEN("A UI Slider with default scale") {
WHEN("No default value is given from config.") {
simple_option.default_value = nullptr;
auto test_field {Slic3r::GUI::UI_Slider(wxTheApp->GetTopWindow(), simple_option)};
THEN("Initial value of slider is 0.") {
REQUIRE(test_field.get_int() == 0);
REQUIRE(test_field.get_double() == 0);
REQUIRE(test_field.slider()->GetValue() == 0);
REQUIRE(test_field.textctrl()->GetValue() == "0.0");
}
}
WHEN("disable() is called") {
simple_option.default_value = default_color;
auto test_field {Slic3r::GUI::UI_Slider(wxTheApp->GetTopWindow(), simple_option)};
test_field.slider()->Enable();
test_field.textctrl()->Enable();
test_field.textctrl()->SetEditable(true);
test_field.disable();
THEN("Internal slider is disabled.") {
REQUIRE(test_field.slider()->IsEnabled() == false);
}
THEN("Internal textctrl is disabled.") {
REQUIRE(test_field.textctrl()->IsEnabled() == false);
REQUIRE(test_field.textctrl()->IsEditable() == false);
}
}
WHEN("enable() is called") {
simple_option.default_value = default_color;
auto test_field {Slic3r::GUI::UI_Slider(wxTheApp->GetTopWindow(), simple_option)};
test_field.slider()->Disable();
test_field.textctrl()->Disable();
test_field.textctrl()->SetEditable(false);
test_field.enable();
THEN("Internal slider is enabled.") {
REQUIRE(test_field.slider()->IsEnabled() == true);
}
THEN("Internal textctrl is enabled.") {
REQUIRE(test_field.textctrl()->IsEnabled() == true);
REQUIRE(test_field.textctrl()->IsEditable() == true);
}
}
}
GIVEN("A UI Slider with scale of 1") {
WHEN("Option min is 0") {
simple_option.default_value = default_color;
simple_option.min = 0;
auto test_field {Slic3r::GUI::UI_Slider(wxTheApp->GetTopWindow(), simple_option, 1)};
WHEN("default of 0 is used.") {
REQUIRE(test_field.slider()->GetMin() == 0);
}
}
WHEN("Option max is 0") {
simple_option.default_value = default_color;
simple_option.max = 0;
auto test_field {Slic3r::GUI::UI_Slider(wxTheApp->GetTopWindow(), simple_option, 1)};
WHEN("default of 10 * 100 = 1000 is used.") {
REQUIRE(test_field.slider()->GetMax() == 100);
}
}
WHEN("Default value is used.") {
simple_option.default_value = default_color;
auto test_field {Slic3r::GUI::UI_Slider(wxTheApp->GetTopWindow(), simple_option, 1)};
THEN("Raw slider value is 30 * scale (1)") {
REQUIRE(test_field.slider()->GetValue() == 30);
}
}
}
}
SCENARIO( "UI_Slider: Event handlers") {
wxTestableFrame* old = dynamic_cast<wxTestableFrame*>(wxTheApp->GetTopWindow());
old->Destroy();
wxTheApp->SetTopWindow(new wxTestableFrame());
wxUIActionSimulator sim;
wxMilliSleep(500);
auto simple_option {ConfigOptionDef()};
auto* default_color {new ConfigOptionString("30")};
simple_option.min = 0;
simple_option.max = 60;
simple_option.default_value = default_color;
auto event_count {0};
auto changefunc {[&event_count] (const std::string& opt_id, const double& color) { event_count++; }};
auto killfunc {[&event_count](const std::string& opt_id) { event_count += 1; }};
GIVEN("A UI Slider") {
auto test_field {Slic3r::GUI::UI_Slider(wxTheApp->GetTopWindow(), simple_option)};
test_field.on_change = changefunc;
test_field.on_kill_focus = killfunc;
WHEN("UI Slider receives a text change event (enter is pressed).") {
event_count = 0;
auto ev {wxCommandEvent(wxEVT_TEXT_ENTER, test_field.textctrl()->GetId())};
ev.SetEventObject(test_field.textctrl());
test_field.textctrl()->ProcessWindowEvent(ev);
THEN("_on_change fires") {
REQUIRE(event_count == 1);
}
}
WHEN("UI Slider textbox receives a text change event (enter is not pressed).") {
event_count = 0;
auto ev {wxCommandEvent(wxEVT_TEXT, test_field.textctrl()->GetId())};
ev.SetEventObject(test_field.textctrl());
test_field.textctrl()->ProcessWindowEvent(ev);
THEN("Nothing happens") {
REQUIRE(event_count == 0);
}
}
WHEN("UI Slider receives a slider changed event.") {
event_count = 0;
auto ev {wxCommandEvent(wxEVT_SLIDER, test_field.slider()->GetId())};
ev.SetEventObject(test_field.slider());
test_field.slider()->ProcessWindowEvent(ev);
THEN("on_change fires") {
REQUIRE(event_count == 1);
}
}
WHEN("UI_Slider text ctrl receives a kill focus event.") {
event_count = 0;
auto ev {wxFocusEvent(wxEVT_KILL_FOCUS, test_field.textctrl()->GetId())};
ev.SetEventObject(test_field.textctrl());
test_field.textctrl()->ProcessWindowEvent(ev);
THEN("_kill_focus and on_change fires") {
REQUIRE(event_count == 2);
}
}
}
}

View File

@ -0,0 +1,156 @@
#include <catch.hpp>
#ifndef WX_PRECOMP
#include "wx/app.h"
#include "wx/sizer.h"
#include "wx/checkbox.h"
#include "wx/uiaction.h"
#endif // WX_PRECOMP
#include <iostream>
#include "testableframe.h"
#include "OptionsGroup/Field.hpp"
#include "ConfigBase.hpp"
using namespace Slic3r::GUI;
SCENARIO( "Spinctrl initializes with default value if available.") {
wxTestableFrame* old = dynamic_cast<wxTestableFrame*>(wxTheApp->GetTopWindow());
old->Destroy();
wxTheApp->SetTopWindow(new wxTestableFrame());
wxMilliSleep(250);
Slic3r::ConfigOptionDef simple_option;
simple_option.type = coInt;
auto* intopt { new Slic3r::ConfigOptionInt(7) };
simple_option.default_value = intopt; // no delete, it's taken care of by ConfigOptionDef
GIVEN ( "A UI Spinctrl") {
auto test_field {Slic3r::GUI::UI_SpinCtrl(wxTheApp->GetTopWindow(), simple_option)};
wxTheApp->GetTopWindow()->Show();
wxTheApp->GetTopWindow()->Fit();
REQUIRE(test_field.get_int() == 7);
}
}
SCENARIO( "Receiving a Spinctrl event") {
wxTestableFrame* old = dynamic_cast<wxTestableFrame*>(wxTheApp->GetTopWindow());
old->Destroy();
wxTheApp->SetTopWindow(new wxTestableFrame());
wxMilliSleep(250);
GIVEN ( "A UI Spinctrl") {
auto exec_counter {0};
auto changefunc {[&exec_counter] (const std::string& opt_id, int value) { exec_counter++; }};
auto test_field {Slic3r::GUI::UI_SpinCtrl(wxTheApp->GetTopWindow(), Slic3r::ConfigOptionDef())};
test_field.on_change = changefunc;
wxTheApp->GetTopWindow()->Show();
wxTheApp->GetTopWindow()->Fit();
WHEN( "A spin event occurs") {
exec_counter = 0;
auto ev {wxSpinEvent(wxEVT_SPINCTRL, test_field.spinctrl()->GetId())};
ev.SetEventObject(test_field.spinctrl());
test_field.spinctrl()->ProcessWindowEvent(ev);
wxYield();
wxMilliSleep(250);
THEN( "on_change is executed.") {
REQUIRE(exec_counter == 1);
}
}
WHEN( "A spin event occurs and change event is disabled") {
exec_counter = 0;
test_field.disable_change_event = false;
auto ev {wxSpinEvent(wxEVT_SPINCTRL, test_field.spinctrl()->GetId())};
ev.SetEventObject(test_field.spinctrl());
test_field.spinctrl()->ProcessWindowEvent(ev);
wxYield();
wxMilliSleep(250);
THEN( "on_change is not executed.") {
REQUIRE(exec_counter == 1);
}
}
}
}
SCENARIO( "Changing the text via entry works on pressing enter") {
wxTestableFrame* old = dynamic_cast<wxTestableFrame*>(wxTheApp->GetTopWindow());
old->Destroy();
wxTheApp->SetTopWindow(new wxTestableFrame());
wxUIActionSimulator sim;
wxMilliSleep(500);
GIVEN ( "A UI Spinctrl") {
auto exec_counter {0};
auto changefunc {[&exec_counter] (const std::string& opt_id, int value) { exec_counter++; }};
auto test_field {Slic3r::GUI::UI_SpinCtrl(wxTheApp->GetTopWindow(), Slic3r::ConfigOptionDef())};
test_field.on_change = changefunc;
wxTheApp->GetTopWindow()->Show();
wxTheApp->GetTopWindow()->Fit();
WHEN( "A number is entered followed by enter key") {
exec_counter = 0;
test_field.spinctrl()->SetFocus();
wxYield();
wxMilliSleep(250);
sim.Char('3');
wxMilliSleep(250);
sim.Char(WXK_RETURN);
wxMilliSleep(250);
wxYield();
THEN( "on_change is executed.") {
REQUIRE(exec_counter == 1);
}
THEN( "get_int returns entered value.") {
REQUIRE(test_field.get_int() == 3);
}
}
WHEN( "A number is entered followed by enter key and change event is disabled") {
exec_counter = 0;
test_field.disable_change_event = true;
test_field.spinctrl()->SetFocus();
wxYield();
wxMilliSleep(250);
sim.Char('3');
wxMilliSleep(250);
sim.Char(WXK_RETURN);
wxMilliSleep(250);
wxYield();
THEN( "on_change is not executed.") {
REQUIRE(exec_counter == 0);
}
THEN( "get_int returns entered value.") {
REQUIRE(test_field.get_int() == 3);
}
}
WHEN( "A number is entered and focus is lost") {
auto killfunc {[&exec_counter](const std::string& opt_id) { exec_counter += 1; }};
test_field.on_kill_focus = killfunc;
auto ev {wxFocusEvent(wxEVT_KILL_FOCUS, test_field.spinctrl()->GetId())};
ev.SetEventObject(test_field.spinctrl());
exec_counter = 0;
test_field.spinctrl()->SetValue(3);
test_field.spinctrl()->SetFocus();
wxYield();
wxMilliSleep(250);
sim.Char('7');
wxYield();
wxMilliSleep(250);
test_field.spinctrl()->ProcessWindowEvent(ev);
wxMilliSleep(250);
wxYield();
THEN( "on_kill_focus is executed and on_change are both executed.") {
REQUIRE(exec_counter == 2);
}
THEN( "get_int returns updated value.") {
REQUIRE(test_field.get_int() == 7);
}
THEN( "get_bool returns 0.") {
REQUIRE(test_field.get_bool() == 0);
}
}
}
}

View File

@ -0,0 +1,219 @@
#include <catch.hpp>
#ifndef WX_PRECOMP
#include "wx/app.h"
#include "wx/sizer.h"
#include "wx/uiaction.h"
#endif // WX_PRECOMP
#include <iostream>
#include <string>
#include "testableframe.h"
#include "OptionsGroup/Field.hpp"
#include "ConfigBase.hpp"
using namespace Slic3r::GUI;
using namespace std::string_literals;
SCENARIO( "TextCtrl initializes with default value if available.") {
wxTestableFrame* old = dynamic_cast<wxTestableFrame*>(wxTheApp->GetTopWindow());
old->Destroy();
wxTheApp->SetTopWindow(new wxTestableFrame());
wxMilliSleep(250);
Slic3r::ConfigOptionDef simple_option;
simple_option.type = coString;
auto* stropt { new Slic3r::ConfigOptionString("7") };
simple_option.default_value = stropt; // no delete, it's taken care of by ConfigOptionDef
GIVEN ( "A UI Textctrl") {
auto test_field {Slic3r::GUI::UI_TextCtrl(wxTheApp->GetTopWindow(), simple_option)};
wxTheApp->GetTopWindow()->Show();
wxTheApp->GetTopWindow()->Fit();
REQUIRE(test_field.get_string() == "7"s);
}
}
SCENARIO( "Receiving a Textctrl event") {
wxTestableFrame* old = dynamic_cast<wxTestableFrame*>(wxTheApp->GetTopWindow());
old->Destroy();
wxTheApp->SetTopWindow(new wxTestableFrame());
wxMilliSleep(250);
GIVEN ( "A UI Textctrl") {
auto exec_counter {0};
auto changefunc {[&exec_counter] (const std::string& opt_id, std::string value) { exec_counter++; }};
auto test_field {Slic3r::GUI::UI_TextCtrl(wxTheApp->GetTopWindow(), Slic3r::ConfigOptionDef())};
test_field.on_change = changefunc;
wxTheApp->GetTopWindow()->Show();
wxTheApp->GetTopWindow()->Fit();
WHEN( "A text event occurs") {
exec_counter = 0;
auto ev {wxCommandEvent(wxEVT_TEXT_ENTER, test_field.textctrl()->GetId())};
ev.SetEventObject(test_field.textctrl());
test_field.textctrl()->ProcessWindowEvent(ev);
wxYield();
wxMilliSleep(250);
THEN( "on_change is executed.") {
REQUIRE(exec_counter == 1);
}
}
WHEN( "A text event occurs and change event is disabled") {
exec_counter = 0;
test_field.disable_change_event = false;
auto ev {wxCommandEvent(wxEVT_TEXT_ENTER, test_field.textctrl()->GetId())};
ev.SetEventObject(test_field.textctrl());
test_field.textctrl()->ProcessWindowEvent(ev);
wxYield();
wxMilliSleep(250);
THEN( "on_change is not executed.") {
REQUIRE(exec_counter == 1);
}
}
}
}
SCENARIO( "TextCtrl: Changing the text via entry works on pressing enter") {
wxTestableFrame* old = dynamic_cast<wxTestableFrame*>(wxTheApp->GetTopWindow());
old->Destroy();
wxTheApp->SetTopWindow(new wxTestableFrame());
wxUIActionSimulator sim;
wxMilliSleep(500);
GIVEN ( "A UI Textctrl") {
auto exec_counter {0};
auto changefunc {[&exec_counter] (const std::string& opt_id, std::string value) { exec_counter++; }};
auto test_field {Slic3r::GUI::UI_TextCtrl(wxTheApp->GetTopWindow(), Slic3r::ConfigOptionDef())};
test_field.on_change = changefunc;
wxTheApp->GetTopWindow()->Show();
wxTheApp->GetTopWindow()->Fit();
WHEN( "A number is entered followed by enter key") {
exec_counter = 0;
test_field.textctrl()->SetFocus();
wxYield();
wxMilliSleep(250);
sim.Char('3');
wxMilliSleep(250);
sim.Char(WXK_RETURN);
wxMilliSleep(250);
wxYield();
THEN( "on_change is executed.") {
REQUIRE(exec_counter == 1);
}
THEN( "get_string returns entered value.") {
REQUIRE(test_field.get_string() == "3"s);
}
}
WHEN( "A number is entered followed by enter key and change event is disabled") {
exec_counter = 0;
test_field.disable_change_event = true;
test_field.textctrl()->SetFocus();
wxYield();
wxMilliSleep(250);
sim.Char('3');
wxMilliSleep(250);
sim.Char(WXK_RETURN);
wxMilliSleep(250);
wxYield();
THEN( "on_change is not executed.") {
REQUIRE(exec_counter == 0);
}
THEN( "get_string returns entered value.") {
REQUIRE(test_field.get_string() == "3"s);
}
}
WHEN( "A number is entered and focus is lost") {
auto killfunc {[&exec_counter](const std::string& opt_id) { exec_counter += 1; }};
test_field.on_kill_focus = killfunc;
auto ev {wxFocusEvent(wxEVT_KILL_FOCUS, test_field.textctrl()->GetId())};
ev.SetEventObject(test_field.textctrl());
exec_counter = 0;
test_field.textctrl()->SetValue("3");
test_field.textctrl()->SetFocus();
wxYield();
wxMilliSleep(250);
sim.Char('7');
wxYield();
wxMilliSleep(250);
test_field.textctrl()->ProcessWindowEvent(ev);
wxMilliSleep(250);
wxYield();
THEN( "on_kill_focus is executed and on_change are both executed.") {
REQUIRE(exec_counter == 2);
}
THEN( "get_string returns updated value.") {
REQUIRE(test_field.get_string() == "7"s);
}
THEN( "get_bool returns 0.") {
REQUIRE(test_field.get_bool() == 0);
}
THEN( "get_int returns 0.") {
REQUIRE(test_field.get_int() == 0);
}
}
}
}
SCENARIO( "Multiline doesn't update other than on focus change.") {
wxTestableFrame* old = dynamic_cast<wxTestableFrame*>(wxTheApp->GetTopWindow());
old->Destroy();
wxTheApp->SetTopWindow(new wxTestableFrame());
wxUIActionSimulator sim;
wxMilliSleep(500);
Slic3r::ConfigOptionDef simple_option;
simple_option.type = coString;
simple_option.multiline = true;
auto* stropt { new Slic3r::ConfigOptionString("7") };
GIVEN ( "A multiline UI Textctrl") {
auto exec_counter {0};
auto changefunc {[&exec_counter] (const std::string& opt_id, std::string value) { exec_counter++; }};
auto test_field {Slic3r::GUI::UI_TextCtrl(wxTheApp->GetTopWindow(), Slic3r::ConfigOptionDef())};
test_field.on_change = changefunc;
wxTheApp->GetTopWindow()->Show();
wxTheApp->GetTopWindow()->Fit();
WHEN( "text is entered and focus is lost") {
auto killfunc {[&exec_counter](const std::string& opt_id) { exec_counter += 1; }};
test_field.on_kill_focus = killfunc;
auto ev {wxFocusEvent(wxEVT_KILL_FOCUS, test_field.textctrl()->GetId())};
ev.SetEventObject(test_field.textctrl());
exec_counter = 0;
test_field.textctrl()->SetFocus();
wxYield();
wxMilliSleep(250);
sim.Char(WXK_LEFT);
sim.Char('7');
wxYield();
sim.Char('7');
wxYield();
wxMilliSleep(250);
test_field.textctrl()->ProcessWindowEvent(ev);
wxYield();
wxMilliSleep(250);
wxYield();
THEN( "on_kill_focus is executed and on_change are both executed.") {
REQUIRE(exec_counter == 2);
}
THEN( "get_string returns updated value.") {
REQUIRE(test_field.get_string() == "77"s);
}
THEN( "get_bool returns 0.") {
REQUIRE(test_field.get_bool() == 0);
}
THEN( "get_int returns 0.") {
REQUIRE(test_field.get_int() == 0);
}
}
}
}

View File

@ -0,0 +1,239 @@
#define CATCH_CONFIG_RUNNER
#include <catch.hpp>
#include "wx/wx.h"
#include "wx/apptrait.h"
#include "testableframe.h"
#undef wxUSE_GUI
#define wxUSE_GUI 1
#undef wxUSE_LOG
#define wxUSE_LOG 0
typedef int (*FilterEventFunc)(wxEvent&);
typedef bool (*ProcessEventFunc)(wxEvent&);
typedef wxApp TestAppBase;
typedef wxGUIAppTraits TestAppTraitsBase;
using namespace std;
// The application class
//
class TestApp : public TestAppBase
{
public:
TestApp();
// standard overrides
virtual bool OnInit();
virtual int OnExit();
#ifdef __WIN32__
virtual wxAppTraits *CreateTraits()
{
// Define a new class just to customize CanUseStderr() behaviour.
class TestAppTraits : public TestAppTraitsBase
{
public:
// We want to always use stderr, tests are also run unattended and
// in this case we really don't want to show any message boxes, as
// wxMessageOutputBest, used e.g. from the default implementation
// of wxApp::OnUnhandledException(), would do by default.
virtual bool CanUseStderr() { return true; }
// Overriding CanUseStderr() is not enough, we also need to
// override this one to avoid returning false from it.
virtual bool WriteToStderr(const wxString& text)
{
wxFputs(text, stderr);
fflush(stderr);
// Intentionally ignore any errors, we really don't want to
// show any message boxes in any case.
return true;
}
};
return new TestAppTraits;
}
#endif // __WIN32__
// Also override this method to avoid showing any dialogs from here -- and
// show some details about the exception along the way.
virtual bool OnExceptionInMainLoop()
{
wxFprintf(stderr, "Unhandled exception in the main loop: %s\n",
Catch::translateActiveException());
throw;
}
// used by events propagation test
virtual int FilterEvent(wxEvent& event);
virtual bool ProcessEvent(wxEvent& event);
void SetFilterEventFunc(FilterEventFunc f) { m_filterEventFunc = f; }
void SetProcessEventFunc(ProcessEventFunc f) { m_processEventFunc = f; }
// In console applications we run the tests directly from the overridden
// OnRun(), but in the GUI ones we run them when we get the first call to
// our EVT_IDLE handler to ensure that we do everything from inside the
// main event loop. This is especially important under wxOSX/Cocoa where
// the main event loop is different from the others but it's also safer to
// do it like this in the other ports as we test the GUI code in the same
// context as it's used usually, in normal programs, and it might behave
// differently without the event loop.
#if wxUSE_GUI
void OnIdle(wxIdleEvent& event)
{
if ( m_runTests )
{
m_runTests = false;
#ifdef __WXOSX__
// we need to wait until the window is activated and fully ready
// otherwise no events can be posted
wxEventLoopBase* const loop = wxEventLoop::GetActive();
if ( loop )
{
loop->DispatchTimeout(1000);
loop->Yield();
}
#endif // __WXOSX__
m_exitcode = RunTests();
ExitMainLoop();
}
event.Skip();
}
virtual int OnRun()
{
if ( TestAppBase::OnRun() != 0 )
m_exitcode = EXIT_FAILURE;
return m_exitcode;
}
#else // !wxUSE_GUI
virtual int OnRun()
{
return RunTests();
}
#endif // wxUSE_GUI/!wxUSE_GUI
private:
int RunTests();
// flag telling us whether we should run tests from our EVT_IDLE handler
bool m_runTests;
// event handling hooks
FilterEventFunc m_filterEventFunc;
ProcessEventFunc m_processEventFunc;
#if wxUSE_GUI
// the program exit code
int m_exitcode;
#endif // wxUSE_GUI
};
wxIMPLEMENT_APP_NO_MAIN(TestApp);
// ----------------------------------------------------------------------------
// TestApp
// ----------------------------------------------------------------------------
TestApp::TestApp()
{
m_runTests = true;
m_filterEventFunc = NULL;
m_processEventFunc = NULL;
#if wxUSE_GUI
m_exitcode = EXIT_SUCCESS;
#endif // wxUSE_GUI
}
// Init
//
bool TestApp::OnInit()
{
// Hack: don't call TestAppBase::OnInit() to let CATCH handle command line.
// Output some important information about the test environment.
#if wxUSE_GUI
cout << "Test program for wxWidgets GUI features\n"
#else
cout << "Test program for wxWidgets non-GUI features\n"
#endif
<< "build: " << WX_BUILD_OPTIONS_SIGNATURE << "\n"
<< "running under " << wxGetOsDescription()
<< " as " << wxGetUserId()
<< ", locale is " << setlocale(LC_ALL, NULL)
<< std::endl;
#if wxUSE_GUI
// create a parent window to be used as parent for the GUI controls
new wxTestableFrame();
Connect(wxEVT_IDLE, wxIdleEventHandler(TestApp::OnIdle));
#ifdef GDK_WINDOWING_X11
XSetErrorHandler(wxTestX11ErrorHandler);
#endif // GDK_WINDOWING_X11
#endif // wxUSE_GUI
return true;
}
// Event handling
int TestApp::FilterEvent(wxEvent& event)
{
if ( m_filterEventFunc )
return (*m_filterEventFunc)(event);
return TestAppBase::FilterEvent(event);
}
bool TestApp::ProcessEvent(wxEvent& event)
{
if ( m_processEventFunc )
return (*m_processEventFunc)(event);
return TestAppBase::ProcessEvent(event);
}
// Run
//
int TestApp::RunTests()
{
#if wxUSE_LOG
// Switch off logging unless --verbose
bool verbose = wxLog::GetVerbose();
wxLog::EnableLogging(verbose);
#else
bool verbose = false;
#endif
// Cast is needed under MSW where Catch also provides an overload taking
// wchar_t, but as it simply converts arguments to char internally anyhow,
// we can just as well always use the char version.
return Catch::Session().run(argc, static_cast<char**>(argv));
}
int TestApp::OnExit()
{
#if wxUSE_GUI
delete GetTopWindow();
#endif // wxUSE_GUI
return TestAppBase::OnExit();
}
int main(int argc, char **argv)
{
return wxEntry(argc, argv);
}

View File

@ -0,0 +1,27 @@
#include <catch.hpp>
#include "misc_ui.hpp"
#include <string>
using namespace std::string_literals;
using namespace Slic3r::GUI;
SCENARIO( "trim_zeroes leaves parsable numbers.") {
auto n192 {"19.200000000"s};
auto n00 {"0.0"s};
auto n0 {"0."s};
REQUIRE(trim_zeroes(n00) == "0.0"s);
REQUIRE(trim_zeroes(n0) == "0.0"s);
REQUIRE(trim_zeroes(n192) == "19.2"s);
}
SCENARIO ( "trim_zeroes doesn't reduce precision.") {
GIVEN( "A number with a long string of zeroes and a 0") {
auto n12 {"19.200000002"s};
auto n120 {"19.2000000020"s};
auto n1200 {"19.20000000200"s};
REQUIRE(trim_zeroes(n12) == "19.200000002"s);
REQUIRE(trim_zeroes(n120) == "19.200000002"s);
REQUIRE(trim_zeroes(n1200) == "19.200000002"s);
}
}

View File

@ -0,0 +1,29 @@
#include <catch.hpp>
#ifndef WX_PRECOMP
#include "wx/app.h"
#include "wx/sizer.h"
#include "wx/uiaction.h"
#endif // WX_PRECOMP
#include <iostream>
#include "OptionsGroup.hpp"
#include "ConfigBase.hpp"
#include "Config.hpp"
using namespace Slic3r::GUI;
SCENARIO("OptionsGroup: Construction.") {
}
SCENARIO("ConfigOptionsGroup: Factory methods") {
GIVEN("A default Slic3r config") {
OptionsGroup optgroup;
WHEN("add_single_option is called for avoid_crossing_perimeters") {
THEN("a UI_Checkbox is added to the field map") {
REQUIRE(optgroup.get_field("avoid_crossing_perimeters") != nullptr);
}
THEN("a ConfigOptionBool is added to the option map") {
REQUIRE(optgroup.get_field("avoid_crossing_perimeters") != nullptr);
}
}
}
}

View File

@ -0,0 +1,195 @@
#include <catch.hpp>
#include "Preset.hpp"
#include "Config.hpp"
#include <test_options.hpp>
using namespace Slic3r::GUI;
using namespace std::literals::string_literals;
SCENARIO( "Preset construction" ) {
GIVEN("A preset file with at least one configuration") {
WHEN( "Preset is constructed." ) {
Preset item {std::string(testfile_dir) + "test_preset", "preset_load_numeric.ini"s, preset_t::Print};
THEN("Name is preset_load_numeric") {
REQUIRE(item.name == "preset_load_numeric"s);
}
THEN("group is Print") {
REQUIRE(item.group == preset_t::Print);
}
THEN("preset default is false.") {
REQUIRE(item.default_preset == false);
}
}
}
GIVEN("A default preset construction for Print") {
WHEN( "Preset is constructed." ) {
Preset item {true, "- default -", preset_t::Print};
THEN("Name is - default -") {
REQUIRE(item.name == "- default -"s);
}
THEN("group is Print") {
REQUIRE(item.group == preset_t::Print);
}
THEN("preset default is true.") {
REQUIRE(item.default_preset == true);
}
THEN("file_exists() is false") {
REQUIRE(item.file_exists() == false);
}
THEN("Does not contain filament_colour (a material option)") {
if (auto config = item.config().lock()) {
REQUIRE(!config->has("filament_colour"));
}
}
THEN("Does not contain gcode_flavor (a printer option)") {
if (auto config = item.config().lock()) {
REQUIRE(!config->has("gcode_flavor"));
}
}
THEN("Does contain layer_height (a print option)") {
if (auto config = item.config().lock()) {
REQUIRE(config->has("layer_height"));
}
}
}
}
GIVEN("A default preset construction for Material") {
WHEN( "Preset is constructed." ) {
Preset item {true, "- default -", preset_t::Material};
THEN("Name is - default -") {
REQUIRE(item.name == "- default -"s);
}
THEN("group is Material") {
REQUIRE(item.group == preset_t::Material);
}
THEN("preset default is true.") {
REQUIRE(item.default_preset == true);
}
THEN("file_exists() is false") {
REQUIRE(item.file_exists() == false);
}
THEN("Does contain filament_colour (a material option)") {
if (auto config = item.config().lock()) {
REQUIRE(config->has("filament_colour"));
}
}
THEN("Does not contain gcode_flavor (a printer option)") {
if (auto config = item.config().lock()) {
REQUIRE(!config->has("gcode_flavor"));
}
}
THEN("Does not contain layer_height (a print option)") {
if (auto config = item.config().lock()) {
REQUIRE(!config->has("layer_height"));
}
}
}
}
GIVEN("A default preset construction for Printer") {
WHEN( "Preset is constructed." ) {
Preset item {true, "- default -", preset_t::Printer};
THEN("Name is - default -") {
REQUIRE(item.name == "- default -"s);
}
THEN("group is Printer") {
REQUIRE(item.group == preset_t::Printer);
}
THEN("preset default is true.") {
REQUIRE(item.default_preset == true);
}
THEN("file_exists() is false") {
REQUIRE(item.file_exists() == false);
}
THEN("Does not contain filament_colour (a material option)") {
if (auto config = item.config().lock()) {
REQUIRE(!config->has("filament_colour"));
}
}
THEN("Does contain gcode_flavor (a printer option)") {
if (auto config = item.config().lock()) {
REQUIRE(config->has("gcode_flavor"));
}
}
THEN("Does not contain layer_height (a print option)") {
if (auto config = item.config().lock()) {
REQUIRE(!config->has("layer_height"));
}
}
}
}
}
SCENARIO( "Preset loading" ) {
GIVEN("A preset file with a config item that has adaptive_slicing = 1") {
Preset item {std::string(testfile_dir) + "test_preset", "preset_load_numeric.ini"s, preset_t::Print};
THEN("file_exists() returns true") {
REQUIRE(item.file_exists() == true);
}
config_ptr ref;
WHEN("The preset file with one item is loaded") {
ref = item.load_config();
THEN("Config is not dirty.") {
REQUIRE(item.dirty() == false);
}
THEN("adaptive_slicing = 1 in the preset config") {
REQUIRE(item.dirty_config().get<ConfigOptionBool>("adaptive_slicing").getBool() == true);
}
}
auto config { Slic3r::Config::new_from_defaults() };
ref = item.load_config();
WHEN("Option is changed in the config via loading a config") {
// precondition: adaptive_slicing is still true
REQUIRE(item.dirty_config().get<ConfigOptionBool>("adaptive_slicing").getBool() == true);
// precondition: default option for adaptice_slicing is false
REQUIRE(config->get<ConfigOptionBool>("adaptive_slicing").getBool() == false);
ref->apply(config);
THEN("Config is dirty.") {
REQUIRE(item.dirty() == true);
}
THEN("adaptive_slicing = 0 in the preset config") {
REQUIRE(item.dirty_config().get<ConfigOptionBool>("adaptive_slicing").getBool() == false);
}
THEN("subsequent calls yield the same reference") {
REQUIRE(item.load_config() == ref);
}
}
}
GIVEN("A preset file with a config item that has adaptive_slicing = 1 and default_preset = true") {
Preset item {std::string(testfile_dir) + "test_preset", "preset_load_numeric.ini"s, preset_t::Print};
item.default_preset = true;
config_ptr ref;
WHEN("The preset file with one item is loaded") {
ref = item.load_config();
THEN("Config is not dirty.") {
REQUIRE(item.dirty() == false);
}
THEN("adaptive_slicing = 1 in the preset config") {
REQUIRE(item.dirty_config().get<ConfigOptionBool>("adaptive_slicing").getBool() == true);
}
THEN("loaded is true") {
REQUIRE(item.loaded() == true);
}
THEN("subsequent calls yield the same reference") {
REQUIRE(item.load_config() == ref);
}
}
WHEN("Option is changed in the config") {
ref = item.load_config();
ref->set("adaptive_slicing", false);
THEN("Config is dirty.") {
REQUIRE(item.dirty() == true);
}
THEN("adaptive_slicing = 0 in the preset config") {
REQUIRE(item.dirty_config().get<ConfigOptionBool>("adaptive_slicing").getBool() == false);
}
}
}
GIVEN("An invalid preset file") {
Preset item {std::string(testfile_dir) + "test_preset", "___invalid__preset_load_numeric.ini"s, preset_t::Print};
THEN("file_exists() returns false") {
REQUIRE(item.file_exists() == false);
}
THEN("loaded is false") {
REQUIRE(item.loaded() == false);
}
}
}

View File

@ -0,0 +1,309 @@
#include <catch.hpp>
#ifndef WX_PRECOMP
#include "wx/app.h"
#include "wx/sizer.h"
#include "wx/uiaction.h"
#endif // WX_PRECOMP
#include <iostream>
#include "testableframe.h"
#include "Log.hpp"
#include "Print.hpp"
#include "Preset.hpp"
#include "Plater/PresetChooser.hpp"
#include "Config.hpp"
#include <test_options.hpp>
using namespace Slic3r::GUI;
using namespace std::literals::string_literals;
std::array<Presets, preset_types> defaults() {
std::array<Presets, preset_types> default_presets;
default_presets[get_preset(preset_t::Print)].push_back(Preset(true, "- default -", preset_t::Print));
default_presets[get_preset(preset_t::Material)].push_back(Preset(true, "- default -", preset_t::Material));
default_presets[get_preset(preset_t::Printer)].push_back(Preset(true, "- default -", preset_t::Printer));
return default_presets;
}
std::array<Presets, preset_types> sample() {
std::array<Presets, preset_types> preset_list;
preset_list[get_preset(preset_t::Print)].push_back(Preset(testfile_dir + "test_preset_chooser"s, "print-profile.ini", preset_t::Print));
preset_list[get_preset(preset_t::Print)].push_back(Preset(true, "- default -", preset_t::Print));
preset_list[get_preset(preset_t::Material)].push_back(Preset(testfile_dir + "test_preset_chooser"s, "material-profile.ini", preset_t::Material));
preset_list[get_preset(preset_t::Material)].push_back(Preset(true, "- default -", preset_t::Material));
preset_list[get_preset(preset_t::Printer)].push_back(Preset(true, "- default -", preset_t::Printer));
preset_list[get_preset(preset_t::Printer)].push_back(Preset(testfile_dir + "test_preset_chooser"s, "printer-profile.ini", preset_t::Printer));
return preset_list;
}
std::array<Presets, preset_types> default_compatible_reversion() {
std::array<Presets, preset_types> preset_list;
preset_list[get_preset(preset_t::Print)].push_back(Preset(testfile_dir + "test_preset_chooser"s, "print-profile.ini", preset_t::Print));
preset_list[get_preset(preset_t::Print)].push_back(Preset(true, "- default -", preset_t::Print));
// set the material to be compatible to printer-profile-2 only
preset_list[get_preset(preset_t::Material)].push_back(Preset(testfile_dir + "test_preset_chooser"s, "incompat-material-profile.ini", preset_t::Material));
preset_list[get_preset(preset_t::Material)].push_back(Preset(true, "- default -", preset_t::Material));
preset_list[get_preset(preset_t::Printer)].push_back(Preset(true, "- default -", preset_t::Printer));
preset_list[get_preset(preset_t::Printer)].push_back(Preset(testfile_dir + "test_preset_chooser"s, "printer-profile-2.ini", preset_t::Printer));
preset_list[get_preset(preset_t::Printer)].push_back(Preset(testfile_dir + "test_preset_chooser"s, "printer-profile.ini", preset_t::Printer));
return preset_list;
}
std::array<Presets, preset_types> compatible_reversion() {
std::array<Presets, preset_types> preset_list;
preset_list[get_preset(preset_t::Print)].push_back(Preset(testfile_dir + "test_preset_chooser"s, "print-profile.ini", preset_t::Print));
preset_list[get_preset(preset_t::Print)].push_back(Preset(true, "- default -", preset_t::Print));
// set the material to be compatible to printer-profile-2 only
preset_list[get_preset(preset_t::Material)].push_back(Preset(testfile_dir + "test_preset_chooser"s, "incompat-material-profile.ini", preset_t::Material));
preset_list[get_preset(preset_t::Material)].push_back(Preset(testfile_dir + "test_preset_chooser"s, "material-profile.ini", preset_t::Material));
preset_list[get_preset(preset_t::Material)].push_back(Preset(testfile_dir + "test_preset_chooser"s, "other-material-profile.ini", preset_t::Material));
preset_list[get_preset(preset_t::Material)].push_back(Preset(true, "- default -", preset_t::Material));
preset_list[get_preset(preset_t::Printer)].push_back(Preset(true, "- default -", preset_t::Printer));
preset_list[get_preset(preset_t::Printer)].push_back(Preset(testfile_dir + "test_preset_chooser"s, "printer-profile-2.ini", preset_t::Printer));
preset_list[get_preset(preset_t::Printer)].push_back(Preset(testfile_dir + "test_preset_chooser"s, "printer-profile.ini", preset_t::Printer));
return preset_list;
}
// Tests to cover behavior when the printer profile is changed.
// System should update its selected choosers based on changed print profile,
// update settings, etc.
SCENARIO( "PresetChooser changed printer") {
std::shared_ptr<Print> fake_print {std::make_shared<Print>()};
Settings default_settings;
wxUIActionSimulator sim;
wxTestableFrame* old = dynamic_cast<wxTestableFrame*>(wxTheApp->GetTopWindow());
old->Destroy();
wxTheApp->SetTopWindow(new wxTestableFrame());
GIVEN( "A PresetChooser with printer-profile selected." ) {
Settings test_settings;
test_settings.default_presets.at(get_preset(preset_t::Printer)).push_back(wxString("printer-profile"));
auto preset_list {default_compatible_reversion()};
PresetChooser cut(wxTheApp->GetTopWindow(), fake_print, &test_settings, preset_list);
cut.load();
WHEN( "Printer profile is changed to printer-profile-2 via select_preset_by_name" ) {
cut.select_preset_by_name("printer-profile-2", preset_t::Printer, 0);
THEN( "Selected printer profile entry is \"printer-profile-2\"" ) {
for (auto* chooser : cut.preset_choosers[get_preset(preset_t::Printer)]) {
REQUIRE(chooser->GetString(chooser->GetSelection()) == wxString("printer-profile-2"));
}
}
THEN( "Print profile chooser has 1 entry" ) {
for (auto* chooser : cut.preset_choosers[get_preset(preset_t::Print)]) {
REQUIRE(chooser->GetCount() == 1);
}
}
THEN( "Selected print profile entry is \"print-profile\"" ) {
for (auto* chooser : cut.preset_choosers[get_preset(preset_t::Print)]) {
REQUIRE(chooser->GetString(chooser->GetSelection()) == wxString("print-profile"));
}
}
THEN( "Material profile chooser has one entry" ) {
for (auto* chooser : cut.preset_choosers[get_preset(preset_t::Material)]) {
REQUIRE(chooser->GetCount() == 1);
}
}
THEN( "Selected material profile entry is \"material-profile\"" ) {
for (auto* chooser : cut.preset_choosers[get_preset(preset_t::Material)]) {
REQUIRE(chooser->GetString(chooser->GetSelection()) == wxString("incompat-material-profile"));
}
}
}
WHEN( "Printer profile is changed to printer-profile-2 via combobox event" ) {
auto* printer_chooser = cut.preset_choosers[get_preset(preset_t::Printer)].at(0);
printer_chooser->SetSelection(0);
auto ev {wxCommandEvent(wxEVT_COMBOBOX, printer_chooser->GetId())};
ev.SetEventObject(printer_chooser);
printer_chooser->ProcessWindowEvent(ev);
wxYield();
wxMilliSleep(150);
THEN( "Selected printer profile entry is \"printer-profile-2\"" ) {
for (auto* chooser : cut.preset_choosers[get_preset(preset_t::Printer)]) {
REQUIRE(chooser->GetString(chooser->GetSelection()) == wxString("printer-profile-2"));
}
}
THEN( "Print profile chooser has 1 entry" ) {
for (auto* chooser : cut.preset_choosers[get_preset(preset_t::Print)]) {
REQUIRE(chooser->GetCount() == 1);
}
}
THEN( "Selected print profile entry is \"print-profile\"" ) {
for (auto* chooser : cut.preset_choosers[get_preset(preset_t::Print)]) {
REQUIRE(chooser->GetString(chooser->GetSelection()) == wxString("print-profile"));
}
}
THEN( "Material profile chooser has one entry" ) {
for (auto* chooser : cut.preset_choosers[get_preset(preset_t::Material)]) {
REQUIRE(chooser->GetCount() == 1);
}
}
THEN( "Selected material profile entry is \"material-profile\"" ) {
for (auto* chooser : cut.preset_choosers[get_preset(preset_t::Material)]) {
REQUIRE(chooser->GetString(chooser->GetSelection()) == wxString("incompat-material-profile"));
}
}
}
}
GIVEN( "A PresetChooser with printer-profile selected and 2+ non-default entries for material ." ) {
Settings test_settings;
test_settings.default_presets.at(get_preset(preset_t::Printer)).push_back(wxString("printer-profile"));
auto preset_list {compatible_reversion()};
PresetChooser cut(wxTheApp->GetTopWindow(), fake_print, &test_settings, preset_list);
cut.load();
WHEN( "Printer profile has only 2 compatible materials" ) {
THEN( "Material profile chooser has two entries" ) {
for (auto* chooser : cut.preset_choosers[get_preset(preset_t::Material)]) {
REQUIRE(chooser->GetCount() == 2);
}
}
THEN( "incompat-material-profile is not in the chooser" ) {
for (auto* chooser : cut.preset_choosers[get_preset(preset_t::Material)]) {
REQUIRE(chooser->FindString("incompat-material-profile") == wxNOT_FOUND);
}
}
}
WHEN( "Printer profile is changed to printer-profile-2 via select_preset_by_name" ) {
cut.select_preset_by_name("printer-profile-2", preset_t::Printer, 0);
THEN( "Selected printer profile entry is \"printer-profile-2\"" ) {
for (auto* chooser : cut.preset_choosers[get_preset(preset_t::Printer)]) {
REQUIRE(chooser->GetString(chooser->GetSelection()) == wxString("printer-profile-2"));
}
}
THEN( "Print profile chooser has 1 entry" ) {
for (auto* chooser : cut.preset_choosers[get_preset(preset_t::Print)]) {
REQUIRE(chooser->GetCount() == 1);
}
}
THEN( "Selected print profile entry is \"print-profile\"" ) {
for (auto* chooser : cut.preset_choosers[get_preset(preset_t::Print)]) {
REQUIRE(chooser->GetString(chooser->GetSelection()) == wxString("print-profile"));
}
}
THEN( "Material profile chooser has one entry" ) {
for (auto* chooser : cut.preset_choosers[get_preset(preset_t::Material)]) {
REQUIRE(chooser->GetCount() == 3);
}
}
}
}
}
SCENARIO( "PresetChooser Preset loading" ) {
std::shared_ptr<Print> fake_print {std::make_shared<Print>()};
Settings default_settings;
auto& settings_presets = default_settings.default_presets;
wxUIActionSimulator sim;
wxTestableFrame* old = dynamic_cast<wxTestableFrame*>(wxTheApp->GetTopWindow());
old->Destroy();
wxTheApp->SetTopWindow(new wxTestableFrame());
GIVEN( "A PresetChooser object." ) {
WHEN( "load() is called with only default presets" ) {
auto preset_list {defaults()};
PresetChooser cut(wxTheApp->GetTopWindow(), fake_print, &default_settings, preset_list);
cut.load();
THEN( "Number of preset choosers created is 3" ) {
REQUIRE(cut.preset_choosers.size() == 3);
}
THEN( "Each profile chooser has 1 entry" ) {
for (auto chooser_list : cut.preset_choosers) {
REQUIRE(chooser_list.size() == 1);
for (auto* chooser : chooser_list) {
REQUIRE(chooser->GetCount() == 1);
}
}
}
THEN( "Selection mapping table has 3 profile entries, all named \"- default - \"") {
for (const auto& group : { preset_t::Print, preset_t::Material, preset_t::Printer }) {
REQUIRE(cut._chooser_names()[get_preset(group)].at(0) == wxString("- default -"));
}
}
}
WHEN( "load is called with non-default presets and default presets" ) {
auto preset_list {sample()};
PresetChooser cut(wxTheApp->GetTopWindow(), fake_print, &default_settings, preset_list);
cut.load();
THEN( "Number of preset choosers created is 3" ) {
REQUIRE(cut.preset_choosers.size() == 3);
}
THEN( "Each profile chooser has 1 entry" ) {
for (auto chooser_list : cut.preset_choosers) {
REQUIRE(chooser_list.size() == 1);
for (auto* chooser : chooser_list) {
REQUIRE(chooser->GetCount() == 1);
}
}
}
THEN( "Selection mapping table has 3 profile entries, none named \"- default - \"") {
for (const auto& group : { preset_t::Print, preset_t::Material, preset_t::Printer }) {
REQUIRE(cut._chooser_names()[get_preset(group)].at(0) != wxString("- default -"));
}
}
THEN( "Settings are updated to match selected." ) {
REQUIRE(settings_presets[get_preset(preset_t::Print)].at(0) == wxString("print-profile"));
REQUIRE(settings_presets[get_preset(preset_t::Printer)].at(0) == wxString("printer-profile"));
REQUIRE(settings_presets[get_preset(preset_t::Material)].at(0) == wxString("material-profile"));
}
}
}
GIVEN( "A PresetChooser object and a Settings indicating that print-profile is the default option." ) {
Settings test_settings;
test_settings.default_presets.at(get_preset(preset_t::Printer)).push_back(wxString("printer-profile"));
auto preset_list {default_compatible_reversion()};
PresetChooser cut(wxTheApp->GetTopWindow(), fake_print, &test_settings, preset_list);
WHEN( "load is called with non-default presets and default presets and the material is listed with an incompatible printer" ) {
cut.load();
THEN( "Number of preset choosers created is 3" ) {
REQUIRE(cut.preset_choosers.size() == 3);
}
THEN( "Print profile chooser has 1 entry" ) {
for (auto* chooser : cut.preset_choosers[get_preset(preset_t::Print)]) {
REQUIRE(chooser->GetCount() == 1);
}
}
THEN( "Printer profile chooser has 2 entries" ) {
for (auto* chooser : cut.preset_choosers[get_preset(preset_t::Printer)]) {
REQUIRE(chooser->GetCount() == 2);
}
}
THEN( "Material profile chooser has one entry" ) {
for (auto* chooser : cut.preset_choosers[get_preset(preset_t::Material)]) {
REQUIRE(chooser->GetCount() == 1);
}
}
THEN( "Selected printer profile entry is \"printer-profile\"" ) {
for (auto* chooser : cut.preset_choosers[get_preset(preset_t::Printer)]) {
REQUIRE(chooser->GetString(chooser->GetSelection()) == wxString("printer-profile"));
}
}
THEN( "Print profile entry has one entry named \"print-profile\"" ) {
REQUIRE(cut._chooser_names()[get_preset(preset_t::Print)].at(0) == wxString("print-profile"));
}
THEN( "Printer profile entry has an entry named \"printer-profile\"" ) {
REQUIRE(cut._chooser_names()[get_preset(preset_t::Printer)].at(1) == wxString("printer-profile"));
}
THEN( "Printer profile entry has an entry named \"printer-profile\"" ) {
REQUIRE(cut._chooser_names()[get_preset(preset_t::Printer)].at(0) == wxString("printer-profile-2"));
}
THEN( "Material profile entry has one entry named \"- default -\"" ) {
REQUIRE(cut._chooser_names()[get_preset(preset_t::Material)].at(0) == wxString("- default -"));
}
}
}
}

View File

@ -0,0 +1,61 @@
///////////////////////////////////////////////////////////////////////////////
// Name: testableframe.cpp
// Purpose: An improved wxFrame for unit-testing
// Author: Steven Lamerton
// Copyright: (c) 2010 Steven Lamerton
// Licence: wxWindows licence
///////////////////////////////////////////////////////////////////////////////
#ifdef __BORLANDC__
#pragma hdrstop
#endif
#include "wx/app.h"
#include "testableframe.h"
wxTestableFrame::wxTestableFrame() : wxFrame(NULL, wxID_ANY, "Test Frame")
{
// Use fixed position to facilitate debugging.
Move(200, 200);
Show();
}
void wxTestableFrame::OnEvent(wxEvent& evt)
{
m_count[evt.GetEventType()]++;
if(! evt.IsCommandEvent() )
evt.Skip();
}
int wxTestableFrame::GetEventCount(wxEventType type)
{
return m_count[type];
}
void wxTestableFrame::ClearEventCount(wxEventType type)
{
m_count[type] = 0;
}
EventCounter::EventCounter(wxWindow* win, wxEventType type) : m_type(type),
m_win(win)
{
m_frame = wxStaticCast(wxTheApp->GetTopWindow(), wxTestableFrame);
m_win->Connect(m_type, wxEventHandler(wxTestableFrame::OnEvent),
NULL, m_frame);
}
EventCounter::~EventCounter()
{
m_win->Disconnect(m_type, wxEventHandler(wxTestableFrame::OnEvent),
NULL, m_frame);
//This stops spurious counts from previous tests
Clear();
m_frame = NULL;
m_win = NULL;
}

View File

@ -0,0 +1,42 @@
///////////////////////////////////////////////////////////////////////////////
// Name: testableframe.h
// Purpose: An improved wxFrame for unit-testing
// Author: Steven Lamerton
// Copyright: (c) 2010 Steven Lamerton
// Licence: wxWindows licence
///////////////////////////////////////////////////////////////////////////////
#include "wx/frame.h"
#include "wx/hashmap.h"
#include "wx/event.h"
class wxTestableFrame : public wxFrame
{
public:
wxTestableFrame();
void OnEvent(wxEvent& evt);
private:
friend class EventCounter;
int GetEventCount(wxEventType type);
void ClearEventCount(wxEventType type);
wxLongToLongHashMap m_count;
};
class EventCounter
{
public:
EventCounter(wxWindow* win, wxEventType type);
~EventCounter();
int GetCount() { return m_frame->GetEventCount(m_type); }
void Clear() { m_frame->ClearEventCount(m_type); }
private:
wxEventType m_type;
wxTestableFrame* m_frame;
wxWindow* m_win;
};

BIN
src/test/inputs/20mmbox.stl Normal file

Binary file not shown.

21
src/test/inputs/README.md Normal file
View File

@ -0,0 +1,21 @@
Directory containing test-specific input files that are not source code.
---
Rules
===
* Each folder shall be named for the test that it supports.
* `test_config` directory supports `test_config.cpp`, etc.
* If a specific test file is reused across multiple tests, it should go in the `common` directory.
* Each extra input file should be named in such a way that it is relevant to the specific feature of it.
* No files should have special characters in the name (unless those special characters are part of the test).
* No spaces should be in the file paths (again, unless testing the presence of spaces is part of the test).
* Input files that are 3D models should be as small/specific as possible.
* Do not add copyrighted models without permission from the copyright holder.
* Do not add NonCommercial models, these would need to be relicensed from the original holder.
* Add any necessary licensing or attributation information as `<filename>.license`
* Example: A CC-By-SA 3D model called `cool_statue_bro.stl` should have its attributation/license information included in a file called `cool_statue_bro.license`
* Any submitted files without an accompanying `.license` file are assumed to be licensed under [CC-By-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/us/).
* The author of a commit adding any extra input files asserts that, via the process of committing those files, that they
* have abided by the terms of all licensing agreements involved with the files added or
* are the author of the files in question and submit them to the Slic3r project under the terms of [CC-By-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/us/).

View File

@ -0,0 +1,86 @@
solid Default
facet normal 0.000000e+00 0.000000e+00 -1.000000e+00
outer loop
vertex 1.000000e+01 1.000000e+01 0.000000e+00
vertex -1.000000e+01 -1.000000e+01 0.000000e+00
vertex -1.000000e+01 1.000000e+01 0.000000e+00
endloop
endfacet
facet normal 0.000000e+00 0.000000e+00 -1.000000e+00
outer loop
vertex -1.000000e+01 -1.000000e+01 0.000000e+00
vertex 1.000000e+01 1.000000e+01 0.000000e+00
vertex 1.000000e+01 -1.000000e+01 0.000000e+00
endloop
endfacet
facet normal 0.000000e+00 0.000000e+00 1.000000e+00
outer loop
vertex 1.000000e+01 -1.000000e+01 1.000000e+01
vertex -1.000000e+01 1.000000e+01 1.000000e+01
vertex -1.000000e+01 -1.000000e+01 1.000000e+01
endloop
endfacet
facet normal 0.000000e+00 0.000000e+00 1.000000e+00
outer loop
vertex -1.000000e+01 1.000000e+01 1.000000e+01
vertex 1.000000e+01 -1.000000e+01 1.000000e+01
vertex 1.000000e+01 1.000000e+01 1.000000e+01
endloop
endfacet
facet normal 1.000000e+00 0.000000e+00 0.000000e+00
outer loop
vertex 1.000000e+01 1.000000e+01 0.000000e+00
vertex 1.000000e+01 -1.000000e+01 1.000000e+01
vertex 1.000000e+01 -1.000000e+01 0.000000e+00
endloop
endfacet
facet normal 1.000000e+00 0.000000e+00 0.000000e+00
outer loop
vertex 1.000000e+01 -1.000000e+01 1.000000e+01
vertex 1.000000e+01 1.000000e+01 0.000000e+00
vertex 1.000000e+01 1.000000e+01 1.000000e+01
endloop
endfacet
facet normal -0.000000e+00 -1.000000e+00 -0.000000e+00
outer loop
vertex 1.000000e+01 -1.000000e+01 1.000000e+01
vertex -1.000000e+01 -1.000000e+01 0.000000e+00
vertex 1.000000e+01 -1.000000e+01 0.000000e+00
endloop
endfacet
facet normal -0.000000e+00 -1.000000e+00 -0.000000e+00
outer loop
vertex -1.000000e+01 -1.000000e+01 0.000000e+00
vertex 1.000000e+01 -1.000000e+01 1.000000e+01
vertex -1.000000e+01 -1.000000e+01 1.000000e+01
endloop
endfacet
facet normal -1.000000e+00 0.000000e+00 0.000000e+00
outer loop
vertex -1.000000e+01 1.000000e+01 1.000000e+01
vertex -1.000000e+01 -1.000000e+01 0.000000e+00
vertex -1.000000e+01 -1.000000e+01 1.000000e+01
endloop
endfacet
facet normal -1.000000e+00 0.000000e+00 0.000000e+00
outer loop
vertex -1.000000e+01 -1.000000e+01 0.000000e+00
vertex -1.000000e+01 1.000000e+01 1.000000e+01
vertex -1.000000e+01 1.000000e+01 0.000000e+00
endloop
endfacet
facet normal 0.000000e+00 1.000000e+00 0.000000e+00
outer loop
vertex -1.000000e+01 1.000000e+01 1.000000e+01
vertex 1.000000e+01 1.000000e+01 0.000000e+00
vertex -1.000000e+01 1.000000e+01 0.000000e+00
endloop
endfacet
facet normal 0.000000e+00 1.000000e+00 0.000000e+00
outer loop
vertex 1.000000e+01 1.000000e+01 0.000000e+00
vertex -1.000000e+01 1.000000e+01 1.000000e+01
vertex 1.000000e+01 1.000000e+01 1.000000e+01
endloop
endfacet
endsolid Default

View File

@ -0,0 +1,40 @@
# generated by Slic3r 1.3.1-dev on 2018-11-24 17:57:55
bottom_solid_layers = 0
brim_width = 0
complete_objects = 0
external_perimeter_extrusion_width = 0.2
external_perimeters_first = 0
extra_perimeters = 1
extruder_offset = 0x0
extrusion_axis = E
extrusion_width = 0
fill_density = 0%
fill_gaps = 1
first_layer_extrusion_width = 0.2
first_layer_height = 0.3
infill_extrusion_width = 0
interface_shells = 0
interior_brim_width = 0
layer_height = 0.3
min_shell_thickness = 0
only_retract_when_crossing_perimeters = 1
ooze_prevention = 0
perimeter_extrusion_width = 0.2
perimeters = 1
skirts = 0
support_material = 0
support_material_extrusion_width = 0
temperature = 206
thin_walls = 1
threads = 16
top_infill_extrusion_width = 0
top_infill_pattern = rectilinear
top_solid_infill_speed = 15
solid_infill_speed = 15
infill_speed = 15
top_solid_layers = 0
use_firmware_retraction = 1
use_volumetric_e = 1
wipe = 0
xy_size_compensation = 0
z_offset = 0

View File

@ -0,0 +1,252 @@
# generated by Slic3r 1.3.0 on Mon Jul 16 14:02:26 2018
[presets]
filament = - default -
print = Untitled
print_1 = - default -
printer = Untitled
[print:- default -]
adaptive_slicing = 0
adaptive_slicing_quality = 75%
avoid_crossing_perimeters = 0
bottom_infill_pattern = rectilinear
bottom_solid_layers = 3
bridge_acceleration = 0
bridge_flow_ratio = 1
bridge_speed = 60
brim_connections_width = 0
brim_width = 0
compatible_printers =
complete_objects = 0
default_acceleration = 0
dont_support_bridges = 1
external_perimeter_extrusion_width = 0
external_perimeter_speed = 50%
external_perimeters_first = 0
extra_perimeters = 1
extruder_clearance_height = 20
extruder_clearance_radius = 20
extrusion_width = 0
fill_angle = 45
fill_density = 20%
fill_gaps = 1
fill_pattern = stars
first_layer_acceleration = 0
first_layer_extrusion_width = 200%
first_layer_height = 0.35
first_layer_speed = 30
gap_fill_speed = 20
gcode_comments = 0
infill_acceleration = 0
infill_every_layers = 1
infill_extruder = 1
infill_extrusion_width = 0
infill_first = 0
infill_only_where_needed = 0
infill_overlap = 55%
infill_speed = 80
interface_shells = 0
interior_brim_width = 0
layer_height = 0.3
match_horizontal_surfaces = 0
max_print_speed = 80
max_volumetric_speed = 0
min_skirt_length = 0
notes =
only_retract_when_crossing_perimeters = 1
ooze_prevention = 0
output_filename_format = [input_filename_base].gcode
overhangs = 1
perimeter_acceleration = 0
perimeter_extruder = 1
perimeter_extrusion_width = 0
perimeter_speed = 60
perimeters = 3
post_process =
print_settings_id =
raft_layers = 0
regions_overlap = 0
resolution = 0
seam_position = aligned
shortcuts = support_material
skirt_distance = 6
skirt_height = 1
skirts = 1
small_perimeter_speed = 15
solid_infill_below_area = 70
solid_infill_every_layers = 0
solid_infill_extruder = 1
solid_infill_extrusion_width = 0
solid_infill_speed = 20
spiral_vase = 0
standby_temperature_delta = -5
support_material = 1
support_material_angle = 0
support_material_buildplate_only = 0
support_material_contact_distance = 0.2
support_material_enforce_layers = 0
support_material_extruder = 1
support_material_extrusion_width = 0
support_material_interface_extruder = 1
support_material_interface_extrusion_width = 0
support_material_interface_layers = 3
support_material_interface_spacing = 0
support_material_interface_speed = 100%
support_material_max_layers = 0
support_material_pattern = pillars
support_material_spacing = 2.5
support_material_speed = 60
support_material_threshold = 60%
thin_walls = 1
top_infill_extrusion_width = 0
top_infill_pattern = rectilinear
top_solid_infill_speed = 15
top_solid_layers = 3
travel_speed = 130
xy_size_compensation = 0
[print:Untitled]
adaptive_slicing = 0
adaptive_slicing_quality = 75%
avoid_crossing_perimeters = 0
bottom_infill_pattern = rectilinear
bottom_solid_layers = 3
bridge_acceleration = 0
bridge_flow_ratio = 1
bridge_speed = 60
brim_connections_width = 0
brim_width = 0
compatible_printers =
complete_objects = 0
default_acceleration = 0
dont_support_bridges = 1
external_perimeter_extrusion_width = 0
external_perimeter_speed = 50%
external_perimeters_first = 0
extra_perimeters = 1
extruder_clearance_height = 20
extruder_clearance_radius = 20
extrusion_width = 0
fill_angle = 45
fill_density = 20%
fill_gaps = 1
fill_pattern = stars
first_layer_acceleration = 0
first_layer_extrusion_width = 200%
first_layer_height = 0.35
first_layer_speed = 30
gap_fill_speed = 20
gcode_comments = 1
infill_acceleration = 0
infill_every_layers = 1
infill_extruder = 1
infill_extrusion_width = 0
infill_first = 0
infill_only_where_needed = 0
infill_overlap = 55%
infill_speed = 80
interface_shells = 0
interior_brim_width = 0
layer_height = 0.3
match_horizontal_surfaces = 0
max_print_speed = 80
max_volumetric_speed = 0
min_skirt_length = 0
notes =
only_retract_when_crossing_perimeters = 1
ooze_prevention = 0
output_filename_format = [input_filename_base].gcode
overhangs = 1
perimeter_acceleration = 0
perimeter_extruder = 1
perimeter_extrusion_width = 0
perimeter_speed = 60
perimeters = 3
post_process =
print_settings_id =
raft_layers = 0
regions_overlap = 0
resolution = 0
seam_position = aligned
shortcuts = support_material
skirt_distance = 6
skirt_height = 1
skirts = 1
small_perimeter_speed = 15
solid_infill_below_area = 70
solid_infill_every_layers = 0
solid_infill_extruder = 1
solid_infill_extrusion_width = 0
solid_infill_speed = 20
spiral_vase = 0
standby_temperature_delta = -5
support_material = 0
support_material_angle = 0
support_material_buildplate_only = 0
support_material_contact_distance = 0.2
support_material_enforce_layers = 0
support_material_extruder = 1
support_material_extrusion_width = 0
support_material_interface_extruder = 1
support_material_interface_extrusion_width = 0
support_material_interface_layers = 3
support_material_interface_spacing = 0
support_material_interface_speed = 100%
support_material_max_layers = 0
support_material_pattern = pillars
support_material_spacing = 2.5
support_material_speed = 60
support_material_threshold = 60%
thin_walls = 1
top_infill_extrusion_width = 0
top_infill_pattern = rectilinear
top_solid_infill_speed = 15
top_solid_layers = 3
travel_speed = 130
xy_size_compensation = 0
[printer:Untitled]
bed_shape = 0x0,200x0,200x200,0x200
before_layer_gcode =
between_objects_gcode =
end_gcode = M104 S0 ; turn off temperature\nG28 X0 ; home X axis\nM84 ; disable motors\n
extruder_offset = 0x0
gcode_flavor = reprap
has_heatbed = 1
host_type = octoprint
layer_gcode = ; LAYER START [layer_z] [layer_num]
max_layer_height = 0.3
min_layer_height = 0.15
nozzle_diameter = 0.5
octoprint_apikey = dsdsdsd
pressure_advance = 0
print_host = 127.0.0.1
printer_notes =
printer_settings_id =
retract_before_travel = 2
retract_layer_change = 1
retract_length = 2
retract_length_toolchange = 10
retract_lift = 1
retract_lift_above = 0
retract_lift_below = 0
retract_restart_extra = 0
retract_restart_extra_toolchange = 0
retract_speed = 40
serial_port =
serial_speed = 250000
start_gcode = G28 ; home all axes\nG1 Z5 F5000 ; lift nozzle\n
toolchange_gcode =
use_firmware_retraction = 0
use_relative_e_distances = 0
use_set_and_wait_bed = 0
use_set_and_wait_extruder = 0
use_volumetric_e = 0
vibration_limit = 0
wipe = 1
z_offset = 0
z_steps_per_mm = 0
[settings]
autocenter = 1

View File

@ -0,0 +1,167 @@
# generated by Slic3r 1.3.0 on
adaptive_slicing = 0
adaptive_slicing_quality = 75%
avoid_crossing_perimeters = 0
bed_shape = 0x0,200x0,200x200,0x200
bed_temperature = 0
before_layer_gcode =
between_objects_gcode =
bottom_infill_pattern = rectilinear
bottom_solid_layers = 3
bridge_acceleration = 0
bridge_fan_speed = 100
bridge_flow_ratio = 1
bridge_speed = 60
brim_connections_width = 0
brim_width = 0
compatible_printers =
complete_objects = 0
cooling = 1
default_acceleration = 0
disable_fan_first_layers = 3
dont_support_bridges = 1
duplicate_distance = 6
end_filament_gcode = "; Filament-specific end gcode \n;END gcode for filament\n"
end_gcode = M104 S0 ; turn off temperature\nG28 X0 ; home X axis\nM84 ; disable motors\n
external_perimeter_extrusion_width = 0
external_perimeter_speed = 50%
external_perimeters_first = 0
extra_perimeters = 1
extruder_clearance_height = 20
extruder_clearance_radius = 20
extruder_offset = 0x0
extrusion_axis = E
extrusion_multiplier = 1
extrusion_width = 0
fan_always_on = 0
fan_below_layer_time = 60
filament_colour = #FFFFFF
filament_cost = 0
filament_density = 0
filament_diameter = 3
filament_max_volumetric_speed = 0
filament_notes = ""
filament_settings_id =
fill_angle = 45
fill_density = 20%
fill_gaps = 1
fill_pattern = stars
first_layer_acceleration = 0
first_layer_bed_temperature = 0
first_layer_extrusion_width = 200%
first_layer_height = 0.35
first_layer_speed = 30
first_layer_temperature = 200
gap_fill_speed = 20
gcode_arcs = 0
gcode_comments = 1
gcode_flavor = reprap
has_heatbed = 1
host_type = octoprint
infill_acceleration = 0
infill_every_layers = 1
infill_extruder = 1
infill_extrusion_width = 0
infill_first = 0
infill_only_where_needed = 0
infill_overlap = 55%
infill_speed = 80
interface_shells = 0
interior_brim_width = 0
layer_gcode = ; LAYER START [layer_z] [layer_num]
layer_height = 0.3
match_horizontal_surfaces = 0
max_fan_speed = 100
max_layer_height = 0.3
max_print_speed = 80
max_volumetric_speed = 0
min_fan_speed = 35
min_layer_height = 0.15
min_print_speed = 10
min_skirt_length = 0
notes =
nozzle_diameter = 0.5
octoprint_apikey = dsdsdsd
only_retract_when_crossing_perimeters = 1
ooze_prevention = 0
output_filename_format = [input_filename_base].gcode
overhangs = 1
perimeter_acceleration = 0
perimeter_extruder = 1
perimeter_extrusion_width = 0
perimeter_speed = 60
perimeters = 3
post_process =
pressure_advance = 0
print_host = 127.0.0.1
print_settings_id =
printer_notes =
printer_settings_id =
raft_layers = 0
regions_overlap = 0
resolution = 0
retract_before_travel = 2
retract_layer_change = 1
retract_length = 2
retract_length_toolchange = 10
retract_lift = 1
retract_lift_above = 0
retract_lift_below = 0
retract_restart_extra = 0
retract_restart_extra_toolchange = 0
retract_speed = 40
seam_position = aligned
sequential_print_priority = 0
serial_port =
serial_speed = 250000
shortcuts = support_material
skirt_distance = 6
skirt_height = 1
skirts = 1
slowdown_below_layer_time = 5
small_perimeter_speed = 15
solid_infill_below_area = 70
solid_infill_every_layers = 0
solid_infill_extruder = 1
solid_infill_extrusion_width = 0
solid_infill_speed = 20
spiral_vase = 0
standby_temperature_delta = -5
start_filament_gcode = "; Filament gcode\n"
start_gcode = G28 ; home all axes\nG1 Z5 F5000 ; lift nozzle\n
support_material = 0
support_material_angle = 0
support_material_buildplate_only = 0
support_material_contact_distance = 0.2
support_material_enforce_layers = 0
support_material_extruder = 1
support_material_extrusion_width = 0
support_material_interface_extruder = 1
support_material_interface_extrusion_width = 0
support_material_interface_layers = 3
support_material_interface_spacing = 0
support_material_interface_speed = 100%
support_material_max_layers = 0
support_material_pattern = pillars
support_material_spacing = 2.5
support_material_speed = 60
support_material_threshold = 60%
temperature = 200
thin_walls = 1
threads = 8
toolchange_gcode =
top_infill_extrusion_width = 0
top_infill_pattern = rectilinear
top_solid_infill_speed = 15
top_solid_layers = 3
travel_speed = 130
use_firmware_retraction = 0
use_relative_e_distances = 0
use_set_and_wait_bed = 0
use_set_and_wait_extruder = 0
use_volumetric_e = 0
vibration_limit = 0
wipe = 1
xy_size_compensation = 0
z_offset = 0
z_steps_per_mm = 0

View File

@ -0,0 +1,38 @@
# generated by Slic3r 1.3.1-dev on 2018-07-27 01:40:55
before_layer_gcode =
between_objects_gcode =
end_filament_gcode = "; Filament-specific end gcode \n;END gcode for filament\n"
end_gcode = M104 S0 ; turn off temperature\nG28 X0 ; home X axis\nM84 ; disable motors\n
extrusion_axis = E
extrusion_multiplier = 1
filament_cost = 0
filament_density = 0
filament_diameter = 3
filament_max_volumetric_speed = 0
filament_notes = ""
gcode_comments = 0
gcode_flavor = reprap
label_printed_objects = 0
layer_gcode =
max_print_speed = 80
max_volumetric_speed = 0
notes =
pressure_advance = 0
printer_notes =
retract_length = 2
retract_length_toolchange = 10
retract_lift = 1.5
retract_lift_above = 0
retract_lift_below = 0
retract_restart_extra = 0
retract_restart_extra_toolchange = 0
retract_speed = 40
start_filament_gcode = "; Filament gcode\n"
start_gcode = G28 ; home all axes\nG1 Z5 F5000 ; lift nozzle\n
toolchange_gcode =
travel_speed = 130
use_firmware_retraction = 0
use_relative_e_distances = 0
use_set_and_wait_bed = 0
use_set_and_wait_extruder = 0
use_volumetric_e = 0

View File

@ -0,0 +1,2 @@
# generated by joseph lenox
adaptive_slicing = 1

View File

@ -0,0 +1,25 @@
# generated by Slic3r 1.3.0-dev on 2018-03-23 03:49:17
bed_temperature = 70
bridge_fan_speed = 100
compatible_printers = printer-profile-2;nothing
cooling = 1
disable_fan_first_layers = 4
end_filament_gcode = "; Filament-specific end gcode \n;END gcode for filament\n"
extrusion_multiplier = 1
fan_always_on = 1
fan_below_layer_time = 10
filament_colour = #4EFF00
filament_cost = 30
filament_density = 1.25
filament_diameter = 1.73
filament_max_volumetric_speed = 0
filament_notes = ""
filament_settings_id =
first_layer_bed_temperature = 70
first_layer_temperature = 220
max_fan_speed = 100
min_fan_speed = 100
min_print_speed = 10
slowdown_below_layer_time = 60
start_filament_gcode = "; Filament gcode\n; ATOMIC PLA"
temperature = 218

View File

@ -0,0 +1,25 @@
# generated by Slic3r 1.3.0-dev on 2018-03-23 03:49:17
bed_temperature = 70
bridge_fan_speed = 100
compatible_printers =
cooling = 1
disable_fan_first_layers = 4
end_filament_gcode = "; Filament-specific end gcode \n;END gcode for filament\n"
extrusion_multiplier = 1
fan_always_on = 1
fan_below_layer_time = 10
filament_colour = #4EFF00
filament_cost = 30
filament_density = 1.25
filament_diameter = 1.73
filament_max_volumetric_speed = 0
filament_notes = ""
filament_settings_id =
first_layer_bed_temperature = 70
first_layer_temperature = 220
max_fan_speed = 100
min_fan_speed = 100
min_print_speed = 10
slowdown_below_layer_time = 60
start_filament_gcode = "; Filament gcode\n; ATOMIC PLA"
temperature = 218

View File

@ -0,0 +1,98 @@
# generated by Slic3r 1.3.0-dev on 2017-10-30 20:38:52
adaptive_slicing = 0
adaptive_slicing_quality = 75%
avoid_crossing_perimeters = 0
bottom_infill_pattern = rectilinear
bottom_solid_layers = 2
bridge_acceleration = 5000
bridge_flow_ratio = 1
bridge_speed = 60
brim_connections_width = 0
brim_width = 0
compatible_printers =
complete_objects = 0
default_acceleration = 5000
dont_support_bridges = 1
external_perimeter_extrusion_width = 0.8
external_perimeter_speed = 50%
external_perimeters_first = 0
extra_perimeters = 1
extruder_clearance_height = 20
extruder_clearance_radius = 20
extrusion_width = 0
fill_angle = 45
fill_density = 0%
fill_gaps = 1
fill_pattern = honeycomb
first_layer_acceleration = 5000
first_layer_extrusion_width = 200%
first_layer_height = 0.3
first_layer_speed = 30
gap_fill_speed = 20
gcode_comments = 0
infill_acceleration = 5000
infill_every_layers = 1
infill_extruder = 1
infill_extrusion_width = 0
infill_first = 0
infill_only_where_needed = 0
infill_overlap = 15%
infill_speed = 80
interface_shells = 0
interior_brim_width = 0
layer_height = 0.2
match_horizontal_surfaces = 0
max_print_speed = 80
max_volumetric_speed = 0
min_skirt_length = 5
notes =
only_retract_when_crossing_perimeters = 1
ooze_prevention = 0
output_filename_format = [input_filename_base].gcode
overhangs = 1
perimeter_acceleration = 450
perimeter_extruder = 1
perimeter_extrusion_width = 0.8
perimeter_speed = 60
perimeters = 2
post_process =
print_settings_id =
raft_layers = 0
regions_overlap = 0
resolution = 0
seam_position = aligned
shortcuts = support_material
skirt_distance = 2
skirt_height = 1
skirts = 1
small_perimeter_speed = 15
solid_infill_below_area = 70
solid_infill_every_layers = 0
solid_infill_extruder = 1
solid_infill_extrusion_width = 0
solid_infill_speed = 20
spiral_vase = 0
standby_temperature_delta = -5
support_material = 0
support_material_angle = 0
support_material_buildplate_only = 0
support_material_contact_distance = 0.2
support_material_enforce_layers = 0
support_material_extruder = 1
support_material_extrusion_width = 0
support_material_interface_extruder = 1
support_material_interface_extrusion_width = 0
support_material_interface_layers = 3
support_material_interface_spacing = 0
support_material_interface_speed = 100%
support_material_pattern = rectilinear-grid
support_material_spacing = 1
support_material_speed = 60
support_material_threshold = 60%
thin_walls = 1
top_infill_extrusion_width = 0
top_infill_pattern = archimedeanchords
top_solid_infill_speed = 15
top_solid_layers = 0
travel_speed = 130
xy_size_compensation = 0

View File

@ -0,0 +1,37 @@
# generated by Slic3r 1.3.0-dev on 2017-06-25 14:39:57
bed_shape = 79.5618x8.36228,78.2518x16.6329,76.0845x24.7214,73.0836x32.5389,69.282x40,64.7214x47.0228,59.4516x53.5304,53.5304x59.4516,47.0228x64.7214,40x69.282,32.5389x73.0836,24.7214x76.0845,16.6329x78.2518,8.36228x79.5618,0x80,-8.36228x79.5618,-16.6329x78.2518,-24.7214x76.0845,-32.5389x73.0836,-40x69.282,-47.0228x64.7214,-53.5304x59.4516,-59.4516x53.5304,-64.7214x47.0228,-69.282x40,-73.0836x32.5389,-76.0845x24.7214,-78.2518x16.6329,-79.5618x8.36228,-80x0,-79.5618x-8.36228,-78.2518x-16.6329,-76.0845x-24.7214,-73.0836x-32.5389,-69.282x-40,-64.7214x-47.0228,-59.4516x-53.5304,-53.5304x-59.4516,-47.0228x-64.7214,-40x-69.282,-32.5389x-73.0836,-24.7214x-76.0845,-16.6329x-78.2518,-8.36228x-79.5618,0x-80,8.36228x-79.5618,16.6329x-78.2518,24.7214x-76.0845,32.5389x-73.0836,40x-69.282,47.0228x-64.7214,53.5304x-59.4516,59.4516x-53.5304,64.7214x-47.0228,69.282x-40,73.0836x-32.5389,76.0845x-24.7214,78.2518x-16.6329,79.5618x-8.36228,80x0
before_layer_gcode =
between_objects_gcode =
end_gcode = M104 S0 ; turn off temperature\nM140 S0;\nG28 ;home all axis\nM84; disable motors\n
extruder_offset = 0x0
gcode_flavor = smoothie
has_heatbed = 1
host_type = octoprint
layer_gcode =
nozzle_diameter = 0.35
octoprint_apikey =
pressure_advance = 0
print_host =
printer_notes =
printer_settings_id =
retract_before_travel = 1
retract_layer_change = 1
retract_length = 2
retract_length_toolchange = 10
retract_lift = 0
retract_lift_above = 0
retract_lift_below = 0
retract_restart_extra = 0
retract_restart_extra_toolchange = 0
retract_speed = 40
serial_port =
serial_speed = 250000
start_gcode = M106 S0\nG90; set to absolute position\nG28\nM190 S60; preheat bed\nM104 S100; preheat nozzle.\nM190 S[first_layer_bed_temperature]; Wait until heatbed hits [first_layer_bed_temperature] \nG0 X0 Y0 Z0\nM109 S[first_layer_temperature]; heat nozzle to [first_layer_temperature]C;
toolchange_gcode =
use_firmware_retraction = 1
use_relative_e_distances = 0
use_volumetric_e = 1
vibration_limit = 0
wipe = 0
z_offset = 0
z_steps_per_mm = 0

View File

@ -0,0 +1,37 @@
# generated by Slic3r 1.3.0-dev on 2017-06-25 14:39:57
bed_shape = 79.5618x8.36228,78.2518x16.6329,76.0845x24.7214,73.0836x32.5389,69.282x40,64.7214x47.0228,59.4516x53.5304,53.5304x59.4516,47.0228x64.7214,40x69.282,32.5389x73.0836,24.7214x76.0845,16.6329x78.2518,8.36228x79.5618,0x80,-8.36228x79.5618,-16.6329x78.2518,-24.7214x76.0845,-32.5389x73.0836,-40x69.282,-47.0228x64.7214,-53.5304x59.4516,-59.4516x53.5304,-64.7214x47.0228,-69.282x40,-73.0836x32.5389,-76.0845x24.7214,-78.2518x16.6329,-79.5618x8.36228,-80x0,-79.5618x-8.36228,-78.2518x-16.6329,-76.0845x-24.7214,-73.0836x-32.5389,-69.282x-40,-64.7214x-47.0228,-59.4516x-53.5304,-53.5304x-59.4516,-47.0228x-64.7214,-40x-69.282,-32.5389x-73.0836,-24.7214x-76.0845,-16.6329x-78.2518,-8.36228x-79.5618,0x-80,8.36228x-79.5618,16.6329x-78.2518,24.7214x-76.0845,32.5389x-73.0836,40x-69.282,47.0228x-64.7214,53.5304x-59.4516,59.4516x-53.5304,64.7214x-47.0228,69.282x-40,73.0836x-32.5389,76.0845x-24.7214,78.2518x-16.6329,79.5618x-8.36228,80x0
before_layer_gcode =
between_objects_gcode =
end_gcode = M104 S0 ; turn off temperature\nM140 S0;\nG28 ;home all axis\nM84; disable motors\n
extruder_offset = 0x0
gcode_flavor = smoothie
has_heatbed = 1
host_type = octoprint
layer_gcode =
nozzle_diameter = 0.35
octoprint_apikey =
pressure_advance = 0
print_host =
printer_notes =
printer_settings_id =
retract_before_travel = 1
retract_layer_change = 1
retract_length = 2
retract_length_toolchange = 10
retract_lift = 0
retract_lift_above = 0
retract_lift_below = 0
retract_restart_extra = 0
retract_restart_extra_toolchange = 0
retract_speed = 40
serial_port =
serial_speed = 250000
start_gcode = M106 S0\nG90; set to absolute position\nG28\nM190 S60; preheat bed\nM104 S100; preheat nozzle.\nM190 S[first_layer_bed_temperature]; Wait until heatbed hits [first_layer_bed_temperature] \nG0 X0 Y0 Z0\nM109 S[first_layer_temperature]; heat nozzle to [first_layer_temperature]C;
toolchange_gcode =
use_firmware_retraction = 1
use_relative_e_distances = 0
use_volumetric_e = 1
vibration_limit = 0
wipe = 0
z_offset = 0
z_steps_per_mm = 0

View File

@ -0,0 +1,422 @@
solid stdin
facet normal 0.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 2.000000938e+05 1.450000938e+05 1.000001016e+05
vertex 2.000000938e+05 5.500010156e+04 1.000001016e+05
vertex 2.000000938e+05 5.500010156e+04 1.000001016e+05
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 -1.000000000e+00
outer loop
vertex 2.000000938e+05 1.450000938e+05 1.000001016e+05
vertex 1.500000938e+05 1.450000938e+05 1.000001016e+05
vertex 1.500000938e+05 5.500010156e+04 1.000001016e+05
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 1.000000000e+00
outer loop
vertex 2.000000938e+05 5.500010156e+04 1.000001016e+05
vertex 1.500000938e+05 5.500010156e+04 1.000001016e+05
vertex 1.500000938e+05 1.450000938e+05 1.000001016e+05
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 2.000000938e+05 5.500010156e+04 1.000001016e+05
vertex 2.000000938e+05 5.500010156e+04 1.000001016e+05
vertex 1.500000938e+05 5.500010156e+04 1.000001016e+05
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 2.000000938e+05 1.450000938e+05 1.000001016e+05
vertex 2.000000938e+05 1.450000938e+05 1.000001016e+05
vertex 1.500000938e+05 1.450000938e+05 1.000001016e+05
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.000001016e+05 1.450000938e+05 1.015625000e-01
vertex 1.000001016e+05 5.500010156e+04 1.015625000e-01
vertex 1.000001016e+05 5.500010156e+04 1.015625000e-01
endloop
endfacet
facet normal -1.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.000001016e+05 1.450000938e+05 1.015625000e-01
vertex 1.000001016e+05 1.450000938e+05 5.000010156e+04
vertex 1.000001016e+05 5.500010156e+04 5.000010156e+04
endloop
endfacet
facet normal 1.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.000001016e+05 5.500010156e+04 1.015625000e-01
vertex 1.000001016e+05 5.500010156e+04 5.000010156e+04
vertex 1.000001016e+05 1.450000938e+05 5.000010156e+04
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.000001016e+05 5.500010156e+04 1.015625000e-01
vertex 1.000001016e+05 5.500010156e+04 1.015625000e-01
vertex 1.000001016e+05 5.500010156e+04 5.000010156e+04
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.000001016e+05 1.450000938e+05 1.015625000e-01
vertex 1.000001016e+05 1.450000938e+05 1.015625000e-01
vertex 1.000001016e+05 1.450000938e+05 5.000010156e+04
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.000001016e+05 2.000000938e+05 1.450000938e+05
vertex 1.000001016e+05 2.000000938e+05 5.500010156e+04
vertex 1.000001016e+05 2.000000938e+05 5.500010156e+04
endloop
endfacet
facet normal -1.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.000001016e+05 2.000000938e+05 1.450000938e+05
vertex 1.000001016e+05 1.500000938e+05 1.450000938e+05
vertex 1.000001016e+05 1.500000938e+05 5.500010156e+04
endloop
endfacet
facet normal 1.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.000001016e+05 2.000000938e+05 5.500010156e+04
vertex 1.000001016e+05 1.500000938e+05 5.500010156e+04
vertex 1.000001016e+05 1.500000938e+05 1.450000938e+05
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.000001016e+05 2.000000938e+05 5.500010156e+04
vertex 1.000001016e+05 2.000000938e+05 5.500010156e+04
vertex 1.000001016e+05 1.500000938e+05 5.500010156e+04
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.000001016e+05 2.000000938e+05 1.450000938e+05
vertex 1.000001016e+05 2.000000938e+05 1.450000938e+05
vertex 1.000001016e+05 1.500000938e+05 1.450000938e+05
endloop
endfacet
facet normal -1.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 5.000010156e+04 5.000010156e+04 5.000010156e+04
vertex 5.000010156e+04 1.500000938e+05 5.000010156e+04
vertex 5.000010156e+04 1.500000938e+05 1.500000938e+05
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.500000938e+05 1.450000938e+05 1.000001016e+05
vertex 1.500000938e+05 1.450000938e+05 1.000001016e+05
vertex 1.500000938e+05 1.500000938e+05 5.000010156e+04
endloop
endfacet
facet normal 1.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.500000938e+05 5.500010156e+04 1.000001016e+05
vertex 1.500000938e+05 1.450000938e+05 1.000001016e+05
vertex 1.500000938e+05 1.500000938e+05 5.000010156e+04
endloop
endfacet
facet normal 1.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.500000938e+05 5.500010156e+04 1.000001016e+05
vertex 1.500000938e+05 1.500000938e+05 5.000010156e+04
vertex 1.500000938e+05 5.000010156e+04 5.000010156e+04
endloop
endfacet
facet normal 1.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.500000938e+05 5.500010156e+04 1.000001016e+05
vertex 1.500000938e+05 5.000010156e+04 5.000010156e+04
vertex 1.500000938e+05 5.000010156e+04 1.500000938e+05
endloop
endfacet
facet normal 1.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.500000938e+05 1.500000938e+05 1.500000938e+05
vertex 1.500000938e+05 1.450000938e+05 1.000001016e+05
vertex 1.500000938e+05 5.500010156e+04 1.000001016e+05
endloop
endfacet
facet normal 1.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.500000938e+05 5.000010156e+04 1.500000938e+05
vertex 1.500000938e+05 1.500000938e+05 1.500000938e+05
vertex 1.500000938e+05 5.500010156e+04 1.000001016e+05
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.500000938e+05 5.500010156e+04 1.000001016e+05
vertex 1.500000938e+05 5.000010156e+04 1.500000938e+05
vertex 1.500000938e+05 5.500010156e+04 1.000001016e+05
endloop
endfacet
facet normal 0.000000000e+00 -1.000000000e+00 0.000000000e+00
outer loop
vertex 1.500000938e+05 5.000010156e+04 1.500000938e+05
vertex 1.500000938e+05 5.000010156e+04 5.000010156e+04
vertex 5.000010156e+04 5.000010156e+04 5.000010156e+04
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 -9.999999404e-01
outer loop
vertex 1.000001016e+05 1.450000938e+05 5.000010156e+04
vertex 1.500000938e+05 1.500000938e+05 5.000010156e+04
vertex 5.000010156e+04 1.500000938e+05 5.000010156e+04
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 -1.000000000e+00
outer loop
vertex 1.000001016e+05 1.450000938e+05 5.000010156e+04
vertex 5.000010156e+04 1.500000938e+05 5.000010156e+04
vertex 5.000010156e+04 5.000010156e+04 5.000010156e+04
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 -1.000000000e+00
outer loop
vertex 1.000001016e+05 5.500010156e+04 5.000010156e+04
vertex 1.000001016e+05 1.450000938e+05 5.000010156e+04
vertex 5.000010156e+04 5.000010156e+04 5.000010156e+04
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 -1.000000000e+00
outer loop
vertex 1.000001016e+05 5.500010156e+04 5.000010156e+04
vertex 5.000010156e+04 5.000010156e+04 5.000010156e+04
vertex 1.500000938e+05 5.000010156e+04 5.000010156e+04
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 -1.000000000e+00
outer loop
vertex 1.500000938e+05 1.500000938e+05 5.000010156e+04
vertex 1.000001016e+05 1.450000938e+05 5.000010156e+04
vertex 1.000001016e+05 5.500010156e+04 5.000010156e+04
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 -1.000000000e+00
outer loop
vertex 1.500000938e+05 5.000010156e+04 5.000010156e+04
vertex 1.500000938e+05 1.500000938e+05 5.000010156e+04
vertex 1.000001016e+05 5.500010156e+04 5.000010156e+04
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.000001016e+05 5.500010156e+04 5.000010156e+04
vertex 1.500000938e+05 5.000010156e+04 5.000010156e+04
vertex 1.000001016e+05 5.500010156e+04 5.000010156e+04
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 1.000000000e+00
outer loop
vertex 5.000010156e+04 5.000010156e+04 1.500000938e+05
vertex 5.000010156e+04 1.500000938e+05 1.500000938e+05
vertex 1.500000938e+05 1.500000938e+05 1.500000938e+05
endloop
endfacet
facet normal 0.000000000e+00 1.000000000e+00 -2.048000624e-06
outer loop
vertex 1.500000938e+05 1.500000938e+05 1.500000938e+05
vertex 5.000010156e+04 1.500000938e+05 1.500000938e+05
vertex 1.000001016e+05 1.500000938e+05 1.450000938e+05
endloop
endfacet
facet normal 0.000000000e+00 1.000000000e+00 0.000000000e+00
outer loop
vertex 1.000001016e+05 1.500000938e+05 5.500010156e+04
vertex 1.000001016e+05 1.500000938e+05 1.450000938e+05
vertex 5.000010156e+04 1.500000938e+05 5.000010156e+04
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.500000938e+05 1.500000938e+05 1.500000938e+05
vertex 1.000001016e+05 1.500000938e+05 1.450000938e+05
vertex 1.000001016e+05 1.500000938e+05 1.450000938e+05
endloop
endfacet
facet normal 0.000000000e+00 1.000000000e+00 0.000000000e+00
outer loop
vertex 1.500000938e+05 1.500000938e+05 1.500000938e+05
vertex 1.000001016e+05 1.500000938e+05 1.450000938e+05
vertex 1.000001016e+05 1.500000938e+05 5.500010156e+04
endloop
endfacet
facet normal 0.000000000e+00 1.000000000e+00 0.000000000e+00
outer loop
vertex 1.500000938e+05 1.500000938e+05 5.000010156e+04
vertex 1.500000938e+05 1.500000938e+05 1.500000938e+05
vertex 1.000001016e+05 1.500000938e+05 5.500010156e+04
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.500000938e+05 1.500000938e+05 5.000010156e+04
vertex 1.000001016e+05 1.500000938e+05 5.500010156e+04
vertex 1.000001016e+05 1.500000938e+05 5.500010156e+04
endloop
endfacet
facet normal 0.000000000e+00 1.000000000e+00 2.048000624e-06
outer loop
vertex 1.500000938e+05 1.500000938e+05 5.000010156e+04
vertex 1.000001016e+05 1.500000938e+05 5.500010156e+04
vertex 5.000010156e+04 1.500000938e+05 5.000010156e+04
endloop
endfacet
facet normal 0.000000000e+00 1.000000000e+00 0.000000000e+00
outer loop
vertex 1.000001016e+05 1.500000938e+05 1.450000938e+05
vertex 5.000010156e+04 1.500000938e+05 1.500000938e+05
vertex 5.000010156e+04 1.500000938e+05 5.000010156e+04
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 1.000000000e+00
outer loop
vertex 1.500000938e+05 1.500000938e+05 1.500000938e+05
vertex 1.500000938e+05 5.000010156e+04 1.500000938e+05
vertex 5.000010156e+04 5.000010156e+04 1.500000938e+05
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.000001016e+05 1.450000938e+05 5.000010156e+04
vertex 1.000001016e+05 1.450000938e+05 5.000010156e+04
vertex 1.500000938e+05 1.500000938e+05 5.000010156e+04
endloop
endfacet
facet normal 0.000000000e+00 -1.000000000e+00 0.000000000e+00
outer loop
vertex 5.000010156e+04 5.000010156e+04 5.000010156e+04
vertex 5.000010156e+04 5.000010156e+04 1.500000938e+05
vertex 1.500000938e+05 5.000010156e+04 1.500000938e+05
endloop
endfacet
facet normal 1.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.500000938e+05 1.450000938e+05 1.000001016e+05
vertex 1.500000938e+05 1.500000938e+05 1.500000938e+05
vertex 1.500000938e+05 1.500000938e+05 5.000010156e+04
endloop
endfacet
facet normal -1.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 5.000010156e+04 1.500000938e+05 1.500000938e+05
vertex 5.000010156e+04 5.000010156e+04 1.500000938e+05
vertex 5.000010156e+04 5.000010156e+04 5.000010156e+04
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.000001016e+05 1.500000938e+05 1.450000938e+05
vertex 1.000001016e+05 1.500000938e+05 1.450000938e+05
vertex 1.000001016e+05 2.000000938e+05 1.450000938e+05
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.000001016e+05 1.500000938e+05 5.500010156e+04
vertex 1.000001016e+05 1.500000938e+05 5.500010156e+04
vertex 1.000001016e+05 2.000000938e+05 5.500010156e+04
endloop
endfacet
facet normal 1.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.000001016e+05 1.500000938e+05 1.450000938e+05
vertex 1.000001016e+05 2.000000938e+05 1.450000938e+05
vertex 1.000001016e+05 2.000000938e+05 5.500010156e+04
endloop
endfacet
facet normal -1.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.000001016e+05 1.500000938e+05 5.500010156e+04
vertex 1.000001016e+05 2.000000938e+05 5.500010156e+04
vertex 1.000001016e+05 2.000000938e+05 1.450000938e+05
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.000001016e+05 2.000000938e+05 5.500010156e+04
vertex 1.000001016e+05 2.000000938e+05 1.450000938e+05
vertex 1.000001016e+05 2.000000938e+05 1.450000938e+05
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.000001016e+05 1.450000938e+05 5.000010156e+04
vertex 1.000001016e+05 1.450000938e+05 5.000010156e+04
vertex 1.000001016e+05 1.450000938e+05 1.015625000e-01
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.000001016e+05 5.500010156e+04 5.000010156e+04
vertex 1.000001016e+05 5.500010156e+04 5.000010156e+04
vertex 1.000001016e+05 5.500010156e+04 1.015625000e-01
endloop
endfacet
facet normal 1.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.000001016e+05 1.450000938e+05 5.000010156e+04
vertex 1.000001016e+05 1.450000938e+05 1.015625000e-01
vertex 1.000001016e+05 5.500010156e+04 1.015625000e-01
endloop
endfacet
facet normal -1.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.000001016e+05 5.500010156e+04 5.000010156e+04
vertex 1.000001016e+05 5.500010156e+04 1.015625000e-01
vertex 1.000001016e+05 1.450000938e+05 1.015625000e-01
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.000001016e+05 5.500010156e+04 1.015625000e-01
vertex 1.000001016e+05 1.450000938e+05 1.015625000e-01
vertex 1.000001016e+05 1.450000938e+05 1.015625000e-01
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.500000938e+05 1.450000938e+05 1.000001016e+05
vertex 1.500000938e+05 1.450000938e+05 1.000001016e+05
vertex 2.000000938e+05 1.450000938e+05 1.000001016e+05
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.500000938e+05 5.500010156e+04 1.000001016e+05
vertex 1.500000938e+05 5.500010156e+04 1.000001016e+05
vertex 2.000000938e+05 5.500010156e+04 1.000001016e+05
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 1.000000000e+00
outer loop
vertex 1.500000938e+05 1.450000938e+05 1.000001016e+05
vertex 2.000000938e+05 1.450000938e+05 1.000001016e+05
vertex 2.000000938e+05 5.500010156e+04 1.000001016e+05
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 -1.000000000e+00
outer loop
vertex 1.500000938e+05 5.500010156e+04 1.000001016e+05
vertex 2.000000938e+05 5.500010156e+04 1.000001016e+05
vertex 2.000000938e+05 1.450000938e+05 1.000001016e+05
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 2.000000938e+05 5.500010156e+04 1.000001016e+05
vertex 2.000000938e+05 1.450000938e+05 1.000001016e+05
vertex 2.000000938e+05 1.450000938e+05 1.000001016e+05
endloop
endfacet
endsolid

View File

@ -0,0 +1,422 @@
solid stdin
facet normal 1.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 2.000009961e+04 1.450009961e+04 1.000009863e+04
vertex 2.000009961e+04 5.500099609e+03 1.000009863e+04
vertex 2.000009961e+04 5.500099609e+03 1.000010059e+04
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 -1.000000000e+00
outer loop
vertex 2.000009961e+04 1.450009961e+04 1.000009863e+04
vertex 1.500009961e+04 1.450009961e+04 1.000009863e+04
vertex 1.500009961e+04 5.500099609e+03 1.000009863e+04
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 1.000000000e+00
outer loop
vertex 2.000009961e+04 5.500099609e+03 1.000010059e+04
vertex 1.500009961e+04 5.500099609e+03 1.000010059e+04
vertex 1.500009961e+04 1.450009961e+04 1.000010059e+04
endloop
endfacet
facet normal 0.000000000e+00 -1.000000000e+00 0.000000000e+00
outer loop
vertex 2.000009961e+04 5.500099609e+03 1.000010059e+04
vertex 2.000009961e+04 5.500099609e+03 1.000009863e+04
vertex 1.500009961e+04 5.500099609e+03 1.000009863e+04
endloop
endfacet
facet normal 0.000000000e+00 1.000000000e+00 0.000000000e+00
outer loop
vertex 2.000009961e+04 1.450009961e+04 1.000009863e+04
vertex 2.000009961e+04 1.450009961e+04 1.000010059e+04
vertex 1.500009961e+04 1.450009961e+04 1.000010059e+04
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 -1.000000000e+00
outer loop
vertex 1.000009863e+04 1.450009961e+04 9.960937500e-02
vertex 1.000009863e+04 5.500099609e+03 9.960937500e-02
vertex 1.000010059e+04 5.500099609e+03 9.960937500e-02
endloop
endfacet
facet normal -1.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.000009863e+04 1.450009961e+04 9.960937500e-02
vertex 1.000009863e+04 1.450009961e+04 5.000099609e+03
vertex 1.000009863e+04 5.500099609e+03 5.000099609e+03
endloop
endfacet
facet normal 1.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.000010059e+04 5.500099609e+03 9.960937500e-02
vertex 1.000010059e+04 5.500099609e+03 5.000099609e+03
vertex 1.000010059e+04 1.450009961e+04 5.000099609e+03
endloop
endfacet
facet normal 0.000000000e+00 -1.000000000e+00 0.000000000e+00
outer loop
vertex 1.000010059e+04 5.500099609e+03 9.960937500e-02
vertex 1.000009863e+04 5.500099609e+03 9.960937500e-02
vertex 1.000009863e+04 5.500099609e+03 5.000099609e+03
endloop
endfacet
facet normal 0.000000000e+00 1.000000000e+00 0.000000000e+00
outer loop
vertex 1.000009863e+04 1.450009961e+04 9.960937500e-02
vertex 1.000010059e+04 1.450009961e+04 9.960937500e-02
vertex 1.000010059e+04 1.450009961e+04 5.000099609e+03
endloop
endfacet
facet normal 0.000000000e+00 1.000000000e+00 0.000000000e+00
outer loop
vertex 1.000009863e+04 2.000009961e+04 1.450009961e+04
vertex 1.000009863e+04 2.000009961e+04 5.500099609e+03
vertex 1.000010059e+04 2.000009961e+04 5.500099609e+03
endloop
endfacet
facet normal -1.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.000009863e+04 2.000009961e+04 1.450009961e+04
vertex 1.000009863e+04 1.500009961e+04 1.450009961e+04
vertex 1.000009863e+04 1.500009961e+04 5.500099609e+03
endloop
endfacet
facet normal 1.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.000010059e+04 2.000009961e+04 5.500099609e+03
vertex 1.000010059e+04 1.500009961e+04 5.500099609e+03
vertex 1.000010059e+04 1.500009961e+04 1.450009961e+04
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 -1.000000000e+00
outer loop
vertex 1.000010059e+04 2.000009961e+04 5.500099609e+03
vertex 1.000009863e+04 2.000009961e+04 5.500099609e+03
vertex 1.000009863e+04 1.500009961e+04 5.500099609e+03
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 1.000000000e+00
outer loop
vertex 1.000009863e+04 2.000009961e+04 1.450009961e+04
vertex 1.000010059e+04 2.000009961e+04 1.450009961e+04
vertex 1.000010059e+04 1.500009961e+04 1.450009961e+04
endloop
endfacet
facet normal -1.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 5.000099609e+03 5.000099609e+03 5.000099609e+03
vertex 5.000099609e+03 1.500009961e+04 5.000099609e+03
vertex 5.000099609e+03 1.500009961e+04 1.500009961e+04
endloop
endfacet
facet normal 1.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.500009961e+04 1.450009961e+04 1.000009863e+04
vertex 1.500009961e+04 1.450009961e+04 1.000010059e+04
vertex 1.500009961e+04 1.500009961e+04 5.000099609e+03
endloop
endfacet
facet normal 1.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.500009961e+04 5.500099609e+03 1.000009863e+04
vertex 1.500009961e+04 1.450009961e+04 1.000009863e+04
vertex 1.500009961e+04 1.500009961e+04 5.000099609e+03
endloop
endfacet
facet normal 1.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.500009961e+04 5.500099609e+03 1.000009863e+04
vertex 1.500009961e+04 1.500009961e+04 5.000099609e+03
vertex 1.500009961e+04 5.000099609e+03 5.000099609e+03
endloop
endfacet
facet normal 1.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.500009961e+04 5.500099609e+03 1.000009863e+04
vertex 1.500009961e+04 5.000099609e+03 5.000099609e+03
vertex 1.500009961e+04 5.000099609e+03 1.500009961e+04
endloop
endfacet
facet normal 9.999999404e-01 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.500009961e+04 1.500009961e+04 1.500009961e+04
vertex 1.500009961e+04 1.450009961e+04 1.000010059e+04
vertex 1.500009961e+04 5.500099609e+03 1.000010059e+04
endloop
endfacet
facet normal 1.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.500009961e+04 5.000099609e+03 1.500009961e+04
vertex 1.500009961e+04 1.500009961e+04 1.500009961e+04
vertex 1.500009961e+04 5.500099609e+03 1.000010059e+04
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.500009961e+04 5.500099609e+03 1.000009863e+04
vertex 1.500009961e+04 5.000099609e+03 1.500009961e+04
vertex 1.500009961e+04 5.500099609e+03 1.000010059e+04
endloop
endfacet
facet normal 0.000000000e+00 -1.000000000e+00 0.000000000e+00
outer loop
vertex 1.500009961e+04 5.000099609e+03 1.500009961e+04
vertex 1.500009961e+04 5.000099609e+03 5.000099609e+03
vertex 5.000099609e+03 5.000099609e+03 5.000099609e+03
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 -1.000000000e+00
outer loop
vertex 1.000009863e+04 1.450009961e+04 5.000099609e+03
vertex 1.500009961e+04 1.500009961e+04 5.000099609e+03
vertex 5.000099609e+03 1.500009961e+04 5.000099609e+03
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 -1.000000000e+00
outer loop
vertex 1.000009863e+04 1.450009961e+04 5.000099609e+03
vertex 5.000099609e+03 1.500009961e+04 5.000099609e+03
vertex 5.000099609e+03 5.000099609e+03 5.000099609e+03
endloop
endfacet
facet normal -1.777778067e-07 0.000000000e+00 -1.000000000e+00
outer loop
vertex 1.000009863e+04 5.500099609e+03 5.000099609e+03
vertex 1.000009863e+04 1.450009961e+04 5.000099609e+03
vertex 5.000099609e+03 5.000099609e+03 5.000099609e+03
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 -1.000000000e+00
outer loop
vertex 1.000009863e+04 5.500099609e+03 5.000099609e+03
vertex 5.000099609e+03 5.000099609e+03 5.000099609e+03
vertex 1.500009961e+04 5.000099609e+03 5.000099609e+03
endloop
endfacet
facet normal 1.777778351e-07 0.000000000e+00 -9.999999404e-01
outer loop
vertex 1.500009961e+04 1.500009961e+04 5.000099609e+03
vertex 1.000010059e+04 1.450009961e+04 5.000099609e+03
vertex 1.000010059e+04 5.500099609e+03 5.000099609e+03
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 -1.000000000e+00
outer loop
vertex 1.500009961e+04 5.000099609e+03 5.000099609e+03
vertex 1.500009961e+04 1.500009961e+04 5.000099609e+03
vertex 1.000010059e+04 5.500099609e+03 5.000099609e+03
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 1.000000000e+00
outer loop
vertex 1.000009863e+04 5.500099609e+03 5.000099609e+03
vertex 1.500009961e+04 5.000099609e+03 5.000099609e+03
vertex 1.000010059e+04 5.500099609e+03 5.000099609e+03
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 1.000000000e+00
outer loop
vertex 5.000099609e+03 5.000099609e+03 1.500009961e+04
vertex 5.000099609e+03 1.500009961e+04 1.500009961e+04
vertex 1.500009961e+04 1.500009961e+04 1.500009961e+04
endloop
endfacet
facet normal 0.000000000e+00 1.000000000e+00 0.000000000e+00
outer loop
vertex 1.500009961e+04 1.500009961e+04 1.500009961e+04
vertex 5.000099609e+03 1.500009961e+04 1.500009961e+04
vertex 1.000009863e+04 1.500009961e+04 1.450009961e+04
endloop
endfacet
facet normal 0.000000000e+00 9.999999404e-01 0.000000000e+00
outer loop
vertex 1.000009863e+04 1.500009961e+04 5.500099609e+03
vertex 1.000009863e+04 1.500009961e+04 1.450009961e+04
vertex 5.000099609e+03 1.500009961e+04 5.000099609e+03
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 -1.000000000e+00
outer loop
vertex 1.500009961e+04 1.500009961e+04 1.500009961e+04
vertex 1.000009863e+04 1.500009961e+04 1.450009961e+04
vertex 1.000010059e+04 1.500009961e+04 1.450009961e+04
endloop
endfacet
facet normal 0.000000000e+00 9.999999404e-01 0.000000000e+00
outer loop
vertex 1.500009961e+04 1.500009961e+04 1.500009961e+04
vertex 1.000010059e+04 1.500009961e+04 1.450009961e+04
vertex 1.000010059e+04 1.500009961e+04 5.500099609e+03
endloop
endfacet
facet normal 0.000000000e+00 1.000000000e+00 0.000000000e+00
outer loop
vertex 1.500009961e+04 1.500009961e+04 5.000099609e+03
vertex 1.500009961e+04 1.500009961e+04 1.500009961e+04
vertex 1.000010059e+04 1.500009961e+04 5.500099609e+03
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 1.000000000e+00
outer loop
vertex 1.500009961e+04 1.500009961e+04 5.000099609e+03
vertex 1.000010059e+04 1.500009961e+04 5.500099609e+03
vertex 1.000009863e+04 1.500009961e+04 5.500099609e+03
endloop
endfacet
facet normal 0.000000000e+00 1.000000000e+00 0.000000000e+00
outer loop
vertex 1.500009961e+04 1.500009961e+04 5.000099609e+03
vertex 1.000009863e+04 1.500009961e+04 5.500099609e+03
vertex 5.000099609e+03 1.500009961e+04 5.000099609e+03
endloop
endfacet
facet normal 0.000000000e+00 1.000000000e+00 0.000000000e+00
outer loop
vertex 1.000009863e+04 1.500009961e+04 1.450009961e+04
vertex 5.000099609e+03 1.500009961e+04 1.500009961e+04
vertex 5.000099609e+03 1.500009961e+04 5.000099609e+03
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 1.000000000e+00
outer loop
vertex 1.500009961e+04 1.500009961e+04 1.500009961e+04
vertex 1.500009961e+04 5.000099609e+03 1.500009961e+04
vertex 5.000099609e+03 5.000099609e+03 1.500009961e+04
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.000009863e+04 1.450009961e+04 5.000099609e+03
vertex 1.000010059e+04 1.450009961e+04 5.000099609e+03
vertex 1.500009961e+04 1.500009961e+04 5.000099609e+03
endloop
endfacet
facet normal 0.000000000e+00 -1.000000000e+00 0.000000000e+00
outer loop
vertex 5.000099609e+03 5.000099609e+03 5.000099609e+03
vertex 5.000099609e+03 5.000099609e+03 1.500009961e+04
vertex 1.500009961e+04 5.000099609e+03 1.500009961e+04
endloop
endfacet
facet normal 1.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.500009961e+04 1.450009961e+04 1.000010059e+04
vertex 1.500009961e+04 1.500009961e+04 1.500009961e+04
vertex 1.500009961e+04 1.500009961e+04 5.000099609e+03
endloop
endfacet
facet normal -1.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 5.000099609e+03 1.500009961e+04 1.500009961e+04
vertex 5.000099609e+03 5.000099609e+03 1.500009961e+04
vertex 5.000099609e+03 5.000099609e+03 5.000099609e+03
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 1.000000000e+00
outer loop
vertex 1.000010059e+04 1.500009961e+04 1.450009961e+04
vertex 1.000009863e+04 1.500009961e+04 1.450009961e+04
vertex 1.000009863e+04 2.000009961e+04 1.450009961e+04
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 -1.000000000e+00
outer loop
vertex 1.000009863e+04 1.500009961e+04 5.500099609e+03
vertex 1.000010059e+04 1.500009961e+04 5.500099609e+03
vertex 1.000010059e+04 2.000009961e+04 5.500099609e+03
endloop
endfacet
facet normal 1.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.000010059e+04 1.500009961e+04 1.450009961e+04
vertex 1.000010059e+04 2.000009961e+04 1.450009961e+04
vertex 1.000010059e+04 2.000009961e+04 5.500099609e+03
endloop
endfacet
facet normal -1.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.000009863e+04 1.500009961e+04 5.500099609e+03
vertex 1.000009863e+04 2.000009961e+04 5.500099609e+03
vertex 1.000009863e+04 2.000009961e+04 1.450009961e+04
endloop
endfacet
facet normal 0.000000000e+00 1.000000000e+00 0.000000000e+00
outer loop
vertex 1.000010059e+04 2.000009961e+04 5.500099609e+03
vertex 1.000010059e+04 2.000009961e+04 1.450009961e+04
vertex 1.000009863e+04 2.000009961e+04 1.450009961e+04
endloop
endfacet
facet normal 0.000000000e+00 1.000000000e+00 0.000000000e+00
outer loop
vertex 1.000010059e+04 1.450009961e+04 5.000099609e+03
vertex 1.000009863e+04 1.450009961e+04 5.000099609e+03
vertex 1.000009863e+04 1.450009961e+04 9.960937500e-02
endloop
endfacet
facet normal 0.000000000e+00 -1.000000000e+00 0.000000000e+00
outer loop
vertex 1.000009863e+04 5.500099609e+03 5.000099609e+03
vertex 1.000010059e+04 5.500099609e+03 5.000099609e+03
vertex 1.000010059e+04 5.500099609e+03 9.960937500e-02
endloop
endfacet
facet normal 1.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.000010059e+04 1.450009961e+04 5.000099609e+03
vertex 1.000010059e+04 1.450009961e+04 9.960937500e-02
vertex 1.000010059e+04 5.500099609e+03 9.960937500e-02
endloop
endfacet
facet normal -1.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 1.000009863e+04 5.500099609e+03 5.000099609e+03
vertex 1.000009863e+04 5.500099609e+03 9.960937500e-02
vertex 1.000009863e+04 1.450009961e+04 9.960937500e-02
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 -1.000000000e+00
outer loop
vertex 1.000010059e+04 5.500099609e+03 9.960937500e-02
vertex 1.000010059e+04 1.450009961e+04 9.960937500e-02
vertex 1.000009863e+04 1.450009961e+04 9.960937500e-02
endloop
endfacet
facet normal 0.000000000e+00 1.000000000e+00 0.000000000e+00
outer loop
vertex 1.500009961e+04 1.450009961e+04 1.000010059e+04
vertex 1.500009961e+04 1.450009961e+04 1.000009863e+04
vertex 2.000009961e+04 1.450009961e+04 1.000009863e+04
endloop
endfacet
facet normal 0.000000000e+00 -1.000000000e+00 0.000000000e+00
outer loop
vertex 1.500009961e+04 5.500099609e+03 1.000009863e+04
vertex 1.500009961e+04 5.500099609e+03 1.000010059e+04
vertex 2.000009961e+04 5.500099609e+03 1.000010059e+04
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 1.000000000e+00
outer loop
vertex 1.500009961e+04 1.450009961e+04 1.000010059e+04
vertex 2.000009961e+04 1.450009961e+04 1.000010059e+04
vertex 2.000009961e+04 5.500099609e+03 1.000010059e+04
endloop
endfacet
facet normal 0.000000000e+00 0.000000000e+00 -1.000000000e+00
outer loop
vertex 1.500009961e+04 5.500099609e+03 1.000009863e+04
vertex 2.000009961e+04 5.500099609e+03 1.000009863e+04
vertex 2.000009961e+04 1.450009961e+04 1.000009863e+04
endloop
endfacet
facet normal 1.000000000e+00 0.000000000e+00 0.000000000e+00
outer loop
vertex 2.000009961e+04 5.500099609e+03 1.000010059e+04
vertex 2.000009961e+04 1.450009961e+04 1.000010059e+04
vertex 2.000009961e+04 1.450009961e+04 1.000009863e+04
endloop
endfacet
endsolid

View File

@ -0,0 +1,172 @@
# generated by Slic3r 1.3.1-dev on
adaptive_slicing = 0
adaptive_slicing_quality = 75%
avoid_crossing_perimeters = 0
bed_shape = 0x0,200x0,200x200,0x200
bed_temperature = 0
before_layer_gcode =
between_objects_gcode =
bottom_infill_pattern = rectilinear
bottom_solid_layers = 3
bridge_acceleration = 0
bridge_fan_speed = 100
bridge_flow_ratio = 1
bridge_speed = 60
brim_connections_width = 0
brim_width = 0
compatible_printers =
complete_objects = 0
cooling = 1
default_acceleration = 0
disable_fan_first_layers = 3
dont_support_bridges = 1
duplicate_distance = 6
end_filament_gcode = "; Filament-specific end gcode \n;END gcode for filament\n"
end_gcode = M104 S0 ; turn off temperature\nG28 X0 ; home X axis\nM84 ; disable motors\n
external_perimeter_extrusion_width = 0
external_perimeter_speed = 50%
external_perimeters_first = 0
extra_perimeters = 1
extruder_clearance_height = 20
extruder_clearance_radius = 20
extruder_offset = 0x0
extrusion_axis = E
extrusion_multiplier = 1
extrusion_width = 0
fan_always_on = 0
fan_below_layer_time = 60
filament_colour = #FFFFFF
filament_cost = 0
filament_density = 0
filament_diameter = 3
filament_max_volumetric_speed = 0
filament_notes = ""
filament_settings_id =
fill_angle = 45
fill_density = 20%
fill_gaps = 1
fill_pattern = stars
first_layer_acceleration = 0
first_layer_bed_temperature = 0
first_layer_extrusion_width = 200%
first_layer_height = 0.35
first_layer_speed = 30
first_layer_temperature = 200
gap_fill_speed = 20
gcode_arcs = 0
gcode_comments = 0
gcode_flavor = reprap
has_heatbed = 1
host_type = octoprint
infill_acceleration = 0
infill_every_layers = 1
infill_extruder = 1
infill_extrusion_width = 0
infill_first = 0
infill_only_where_needed = 0
infill_overlap = 55%
infill_speed = 80
interface_shells = 0
interior_brim_width = 0
label_printed_objects = 0
layer_gcode =
layer_height = 0.3
match_horizontal_surfaces = 0
max_fan_speed = 100
max_layer_height = 0.3
max_print_speed = 80
max_volumetric_speed = 0
min_fan_speed = 35
min_layer_height = 0.15
min_print_speed = 10
min_shell_thickness = 0
min_skirt_length = 0
min_top_bottom_shell_thickness = 0
notes =
nozzle_diameter = 0.5
octoprint_apikey =
only_retract_when_crossing_perimeters = 1
ooze_prevention = 0
output_filename_format = [input_filename_base].gcode
overhangs = 1
perimeter_acceleration = 0
perimeter_extruder = 1
perimeter_extrusion_width = 0
perimeter_speed = 60
perimeters = 3
post_process =
pressure_advance = 0
print_host =
print_settings_id =
printer_notes =
printer_settings_id =
raft_layers = 0
regions_overlap = 0
resolution = 0
retract_before_travel = 2
retract_layer_change = 0
retract_length = 2
retract_length_toolchange = 10
retract_lift = 0
retract_lift_above = 0
retract_lift_below = 0
retract_restart_extra = 0
retract_restart_extra_toolchange = 0
retract_speed = 40
seam_position = aligned
sequential_print_priority = 0
serial_port =
serial_speed = 250000
shortcuts = support_material
skirt_distance = 6
skirt_height = 1
skirts = 1
slowdown_below_layer_time = 5
small_perimeter_speed = 15
solid_infill_below_area = 70
solid_infill_every_layers = 0
solid_infill_extruder = 1
solid_infill_extrusion_width = 0
solid_infill_speed = 20
spiral_vase = 0
standby_temperature_delta = -5
start_filament_gcode = "; Filament gcode\n"
start_gcode = G28 ; home all axes\nG1 Z5 F5000 ; lift nozzle\n
support_material = 0
support_material_angle = 0
support_material_buildplate_only = 0
support_material_contact_distance = 0.2
support_material_enforce_layers = 0
support_material_extruder = 1
support_material_extrusion_width = 0
support_material_interface_extruder = 1
support_material_interface_extrusion_width = 0
support_material_interface_layers = 3
support_material_interface_spacing = 0
support_material_interface_speed = 100%
support_material_max_layers = 0
support_material_pattern = pillars
support_material_pillar_size = 2.5
support_material_pillar_spacing = 10
support_material_spacing = 2.5
support_material_speed = 60
support_material_threshold = 60%
temperature = 200
thin_walls = 1
threads = 4
toolchange_gcode =
top_infill_extrusion_width = 0
top_infill_pattern = rectilinear
top_solid_infill_speed = 15
top_solid_layers = 3
travel_speed = 130
use_firmware_retraction = 0
use_relative_e_distances = 0
use_set_and_wait_bed = 0
use_set_and_wait_extruder = 0
use_volumetric_e = 0
vibration_limit = 0
wipe = 0
xy_size_compensation = 0
z_offset = 0
z_steps_per_mm = 0

View File

@ -0,0 +1,214 @@
#include <catch.hpp>
#include "../../libslic3r/Config.hpp"
#include "../test_options.hpp"
#include <string>
//
//using namespace Slic3r;
//using namespace std::literals::string_literals;
//
//SCENARIO("Generic config validation performs as expected.") {
// GIVEN("A config generated from default options") {
// auto config {Slic3r::DynamicPrintConfig::new_from_defaults()};
// WHEN( "perimeter_extrusion_width is set to 250%, a valid value") {
// config->set("perimeter_extrusion_width", "250%");
// THEN( "The config is read as valid.") {
// REQUIRE_NOTHROW(config->validate());
// }
// }
// WHEN( "perimeter_extrusion_width is set to -10, an invalid value") {
// config->set("perimeter_extrusion_width", -10);
// THEN( "An InvalidOptionException exception is thrown.") {
// auto except_thrown {false};
// try {
// config->validate();
// } catch (const InvalidOptionException& e) {
// except_thrown = true;
// }
// REQUIRE(except_thrown == true);
// }
// }
//
// WHEN( "perimeters is set to -10, an invalid value") {
// config->set("perimeters", -10);
// THEN( "An InvalidOptionException exception is thrown.") {
// auto except_thrown {false};
// try {
// config->validate();
// } catch (const InvalidOptionException& e) {
// except_thrown = true;
// }
// REQUIRE(except_thrown == true);
// }
// }
// }
//}
//
//SCENARIO("Config accessor functions perform as expected.") {
// GIVEN("A config generated from default options") {
// auto config {Slic3r::DynamicPrintConfig::new_from_defaults()};
// 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->get<ConfigOptionBool>("gcode_comments").getBool() == true);
// }
// }
// WHEN("A boolean option is set to a string value representing a 0 or 1") {
// CHECK_NOTHROW(config->set("gcode_comments", "1"));
// THEN("The underlying value is set correctly.") {
// REQUIRE(config->get<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->get<ConfigOptionBool>("gcode_comments").getBool() == false);
// }
// }
// WHEN("A string 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("bed_temperature", "100");
// THEN("The underlying value is set correctly.") {
// REQUIRE(config->get<ConfigOptionInt>("bed_temperature").getInt() == 100);
// }
// }
//
// 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->get<ConfigOptionInt>("bed_temperature").getInt() == 100);
// }
// }
// 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->get<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->get<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("perimeter_speed", "zzzz"), BadOptionTypeException);
// }
// THEN("The value does not change.") {
// REQUIRE(config->get<ConfigOptionFloat>("perimeter_speed").getFloat() == 60.0);
// }
// }
// WHEN("A string option is set through the string interface") {
// config->set("octoprint_apikey", "100");
// THEN("The underlying value is set correctly.") {
// REQUIRE(config->get<ConfigOptionString>("octoprint_apikey").getString() == "100");
// }
// }
// WHEN("A string option is set through the integer interface") {
// config->set("octoprint_apikey", 100);
// THEN("The underlying value is set correctly.") {
// REQUIRE(config->get<ConfigOptionString>("octoprint_apikey").getString() == "100");
// }
// }
// WHEN("A string option is set through the double interface") {
// config->set("octoprint_apikey", 100.5);
// THEN("The underlying value is set correctly.") {
// REQUIRE(config->get<ConfigOptionString>("octoprint_apikey").getString() == std::to_string(100.5));
// }
// }
// WHEN("A float or percent is set as a percent through the string interface.") {
// config->set("first_layer_extrusion_width", "100%");
// THEN("Value and percent flag are 100/true") {
// auto tmp {config->get<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("first_layer_extrusion_width", "100");
// THEN("Value and percent flag are 100/false") {
// auto tmp {config->get<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->get<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->get<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->get<ConfigOptionString>("deadbeef_invalid_option", false), UnknownOptionException);
// REQUIRE_THROWS_AS(config->get<ConfigOptionFloat>("deadbeef_invalid_option", false), UnknownOptionException);
// REQUIRE_THROWS_AS(config->get<ConfigOptionInt>("deadbeef_invalid_option", false), UnknownOptionException);
// REQUIRE_THROWS_AS(config->get<ConfigOptionBool>("deadbeef_invalid_option", false), UnknownOptionException);
// }
// }
// WHEN("An invalid option is requested during get_ptr.") {
// THEN("A UnknownOptionException exception is thrown.") {
// REQUIRE_THROWS_AS(config->get_ptr<ConfigOptionString>("deadbeef_invalid_option", false), UnknownOptionException);
// REQUIRE_THROWS_AS(config->get_ptr<ConfigOptionFloat>("deadbeef_invalid_option", false), UnknownOptionException);
// REQUIRE_THROWS_AS(config->get_ptr<ConfigOptionInt>("deadbeef_invalid_option", false), UnknownOptionException);
// REQUIRE_THROWS_AS(config->get_ptr<ConfigOptionBool>("deadbeef_invalid_option", false), UnknownOptionException);
// }
// }
//
// WHEN("getX called on an unset option.") {
// THEN("The default is returned.") {
// REQUIRE(config->getFloat("layer_height") == 0.3);
// REQUIRE(config->getInt("raft_layers") == 0);
// REQUIRE(config->getBool("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->getFloat("layer_height") == 0.5);
// }
// }
// }
//}
//
//SCENARIO("Config ini load/save interface", "[!mayfail]") {
// WHEN("new_from_ini is called") {
// auto config {Slic3r::DynamicPrintConfig::new_from_ini(std::string(testfile_dir) + "test_config/new_from_ini.ini"s) };
// THEN("Config object contains ini file options.") {
// }
// }
// REQUIRE(false);
//}

View File

@ -0,0 +1,484 @@
#include <catch.hpp>
#include "../test_data.hpp"
#include "../../libslic3r/Fill/Fill.hpp"
#include "../../libslic3r/Print.hpp"
#include "../../libslic3r/Geometry.hpp"
#include "../../libslic3r/Flow.hpp"
#include "../../libslic3r/ClipperUtils.hpp"
#include "../../libslic3r/SVG.hpp"
using namespace Slic3r;
using namespace Slic3r::Geometry;
bool test_if_solid_surface_filled(const ExPolygon& expolygon, double flow_spacing, double angle = 0, double density = 1.0);
//TEST_CASE("Fill: adjusted solid distance") {
// Print print;
// int surface_width {250};
//
// int distance {Slic3r::Flow::solid_spacing(surface_width, 47)};
//
// REQUIRE(distance == Approx(50));
// REQUIRE(surface_width % distance == 0);
//}
Polylines test(const ExPolygon& poly, Fill &filler, const FillParams &params){
std::cout << "don't connect? " << (params.dont_connect ? "true" : "false") << "\n";
Surface surface{ Slic3r::Surface((stPosTop | stDensSolid), poly) };
return filler.fill_surface(&surface, params);
}
TEST_CASE("Fill: Pattern Path Length") {
Fill* filler = {Slic3r::Fill::new_from_type("rectilinear")};
filler->angle = -(PI) / 2.0;
filler->spacing = 5;
FillParams params;
params.dont_adjust = true;
params.density = filler->spacing / 50.0;
//params.endpoints_overlap = false;
SECTION("Square") {
Points test_set;
test_set.reserve(4);
Points points {Point(0,0), Point(100,0), Point(100,100), Point(0,100)};
for (size_t i = 0; i < 4; ++i) {
std::transform(points.cbegin()+i, points.cend(), std::back_inserter(test_set), [] (const Point& a) -> Point { return Point::new_scale(a); } );
std::transform(points.cbegin(), points.cbegin()+i, std::back_inserter(test_set), [] (const Point& a) -> Point { return Point::new_scale(a); } );
Slic3r::ExPolygon expoly;
expoly.contour = Slic3r::Polygon(test_set);
Polylines paths {test(expoly, *filler, params)};
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") {
Points points {Point(0,0), Point(100,0), Point(150,50), Point(100,100), Point(0,100), Point(-50,50)};
Points test_set;
test_set.reserve(6);
std::transform(points.cbegin(), points.cend(), std::back_inserter(test_set), [] (const Point& a) -> Point { return Point::new_scale(a); } );
Slic3r::ExPolygon expoly;
expoly.contour = Slic3r::Polygon(test_set);
Polylines paths {test(expoly, *filler, params)};
REQUIRE(paths.size() == 1); // one continuous path
}
SECTION("Square with hole") {
Points square { Point(0,0), Point(100,0), Point(100,100), Point(0,100)};
Points hole {Point(25,25), Point(75,25), Point(75,75), Point(25,75) };
std::reverse(hole.begin(), hole.end());
Points test_hole;
Points test_square;
std::transform(square.cbegin(), square.cend(), std::back_inserter(test_square), [] (const Point& a) -> Point { return Point::new_scale(a); } );
std::transform(hole.cbegin(), hole.cend(), std::back_inserter(test_hole), [] (const Point& a) -> Point { return Point::new_scale(a); } );
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}) {
FillParams params_local = params;
params_local.density = filler->spacing / spacing;
filler->angle = angle;
Slic3r::ExPolygon e;
e.contour = Slic3r::Polygon(test_square);
e.holes = Slic3r::Polygons(Slic3r::Polygon(test_hole));
Polylines paths {test(e, *filler, params_local)};
std::cout << "paths.size="<<paths.size() << "\n";
{
std::stringstream stri;
stri << "squarewithhole.svg";
SVG svg(stri.str());
svg.draw(paths);
svg.draw(e);
svg.Close();
}
REQUIRE((paths.size() >= 2 && 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") {
FillParams params_local = params;
Fill* filler_local = { Slic3r::Fill::new_from_type("rectilinear") };
filler_local->angle = (PI/4.0);
params_local.dont_adjust = false;
filler_local->spacing = 0.654498;
//filler_local->endpoints_overlap = unscale(359974);
params_local.density = 1;
filler_local->layer_id = 66;
filler_local->z = 20.15;
Points points {Point(25771516,14142125),Point(14142138,25771515),Point(2512749,14142131),Point(14142125,2512749)};
Slic3r::ExPolygon expoly;
expoly.contour = Slic3r::Polygon(points);
Polylines paths {test(expoly, *filler_local, params_local)};
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") {
Points square { Point::new_scale(0,0), Point::new_scale(50,0), Point::new_scale(50,50), Point::new_scale(0,50)};
ExPolygon expolygon;
expolygon.contour = Slic3r::Polygon(square);
auto filler {Slic3r::Fill::new_from_type("rectilinear")};
filler->bounding_box = expolygon.contour.bounding_box();
filler->angle = 0;
auto surface {Surface((stPosTop|stDensSolid), expolygon)};
auto flow {Slic3r::Flow(0.69, 0.4, 0.50)};
filler->spacing = flow.spacing();
params.density = 1.0;
for (auto angle : { 0.0, 45.0}) {
surface.expolygon.rotate(angle, Point(0,0));
auto paths {filler->fill_surface(&surface, params)};
REQUIRE(paths.size() == 1);
}
}
SECTION("Solid surface fill") {
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;
expolygon.contour = Slic3r::Polygon(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") {
Points points {
Point(59515297,5422499),Point(59531249,5578697),Point(59695801,6123186),
Point(59965713,6630228),Point(60328214,7070685),Point(60773285,7434379),
Point(61274561,7702115),Point(61819378,7866770),Point(62390306,7924789),
Point(62958700,7866744),Point(63503012,7702244),Point(64007365,7434357),
Point(64449960,7070398),Point(64809327,6634999),Point(65082143,6123325),
Point(65245005,5584454),Point(65266967,5422499),Point(66267307,5422499),
Point(66269190,8310081),Point(66275379,17810072),Point(66277259,20697500),
Point(65267237,20697500),Point(65245004,20533538),Point(65082082,19994444),
Point(64811462,19488579),Point(64450624,19048208),Point(64012101,18686514),
Point(63503122,18415781),Point(62959151,18251378),Point(62453416,18198442),
Point(62390147,18197355),Point(62200087,18200576),Point(61813519,18252990),
Point(61274433,18415918),Point(60768598,18686517),Point(60327567,19047892),
Point(59963609,19493297),Point(59695865,19994587),Point(59531222,20539379),
Point(59515153,20697500),Point(58502480,20697500),Point(58502480,5422499)
};
Slic3r::ExPolygon expolygon;
expolygon.contour = Slic3r::Polygon(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") {
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;
expolygon.contour = Slic3r::Polygon(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';
}
*/
//TODO: also check by volume extruded
//TODO: replace the simple area coverage check by one that takes into account the width of the path, not only the default flow spacing
//TODO: test more fills
bool test_if_solid_surface_filled(const ExPolygon& expolygon, double flow_width, double angle, double density) {
auto* filler {Slic3r::Fill::new_from_type("concentricgapfill")};
filler->bounding_box = expolygon.contour.bounding_box();
filler->angle = angle;
FillParams params;
params.dont_adjust = false;
Surface surface((stPosBottom | stDensSolid), expolygon);
//note: here we do flow.width = flow_width , flow.gheight = 0.4, flow.nozzle_size = flow_width;
Flow flow(flow_width, 0.4, flow_width);
filler->spacing = flow.spacing();
params.density = density;
Polylines paths {filler->fill_surface(&surface, 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
std::for_each(paths.begin(), paths.end(), [filler, &grown_paths] (const Slic3r::Polyline& p) {
polygons_append(grown_paths, offset(p, scale_(filler->spacing / 2.0)));
});
ExPolygons uncovered = diff_ex(expolygon, grown_paths, true);
// ignore very small dots
const auto scaled_flow_width { std::pow(scale_(flow_width), 2) };
auto iter {std::remove_if(uncovered.begin(), uncovered.end(), [scaled_flow_width] (const ExPolygon& poly) {
return poly.area() > scaled_flow_width;
}) };
uncovered.erase(iter, uncovered.end());
double uncovered_area = 0;
for (ExPolygon &p : uncovered) uncovered_area += unscaled(unscaled(p.area()));
std::cout << "uncovered size =" << uncovered_area << " / "<< unscaled(unscaled(expolygon.area()))<<"\n";
return uncovered.size() == 0; // solid surface is fully filled
}

View File

@ -0,0 +1,220 @@
#include <catch.hpp>
#include <numeric>
#include <sstream>
#include "../test_data.hpp" // get access to init_print, etc
#include "../../libslic3r/Config.hpp"
#include "../../libslic3r/Model.hpp"
#include "../../libslic3r/Config.hpp"
#include "../../libslic3r/GCodeReader.hpp"
#include "../../libslic3r/Flow.hpp"
#include "../../libslic3r/libslic3r.h"
using namespace Slic3r::Test;
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
DynamicPrintConfig* config {Slic3r::DynamicPrintConfig::new_from_defaults()};
config->set_key_value("skirts", new ConfigOptionInt(1));
config->set_key_value("brim_width", new ConfigOptionFloat(2));
config->set_key_value("perimeters", new ConfigOptionInt(3));
config->set_key_value("fill_density", new ConfigOptionPercent(40));
config->set_key_value("first_layer_height", new ConfigOptionFloatOrPercent(100, true));
WHEN("first layer width set to 2mm") {
Slic3r::Model model;
config->set_key_value("first_layer_extrusion_width", new ConfigOptionFloatOrPercent(2.0, false));
Print print;
Slic3r::Test::init_print(print, { TestMesh::cube_20x20x20 }, model, config);
//std::cout << "model pos: " << model.objects.front()->instances.front()->get_offset().x() << ": " << model.objects.front()->instances.front()->get_offset().x() << "\n";
//Print print;
//for (auto* mo : model.objects)
// print.auto_assign_extruders(mo);
//print.apply(model, *config);
////std::cout << "print volume: " << print.<< ": " << model.objects().front()->copies().front().x() << "\n";
//std::string err = print.validate();
std::vector<double> E_per_mm_bottom;
std::string gcode_filepath("");
Slic3r::Test::gcode(gcode_filepath, print);
GCodeReader parser {Slic3r::GCodeReader()};
const auto layer_height { config->opt_float("layer_height") };
std::string gcode_from_file{ read_to_string(gcode_filepath) };
parser.parse_buffer(gcode_from_file, [&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
std::cout << "line on first layer : " << line.raw()<<"\n";
if (line.extruding(self) && line.dist_XY(self) > 0) {
std::cout << "line sextrusion : " << line.dist_E(self) << "\n";
E_per_mm_bottom.emplace_back(line.dist_E(self) / line.dist_XY(self));
}
}
});
THEN(" First layer width applies to everything on first layer.") {
bool pass = false;
auto avg_E {std::accumulate(E_per_mm_bottom.cbegin(), E_per_mm_bottom.cend(), 0.0) / static_cast<double>(E_per_mm_bottom.size())};
pass = (std::count_if(E_per_mm_bottom.cbegin(), E_per_mm_bottom.cend(), [avg_E] (const double& v) { return v == Approx(avg_E); }) == 0);
REQUIRE(pass == true);
REQUIRE(E_per_mm_bottom.size() > 0); // make sure it actually passed because of extrusion
}
THEN(" First layer width does not apply to upper layer.") {
}
clean_file(gcode_filepath, "gcode");
}
}
}
// needs gcode export
SCENARIO(" Bridge flow specifics.", "[!mayfail]") {
GIVEN("A default config with no cooling and a fixed bridge speed, flow ratio and an overhang mesh.") {
WHEN("bridge_flow_ratio is set to 1.0") {
THEN("Output flow is as expected.") {
}
}
WHEN("bridge_flow_ratio is set to 0.5") {
THEN("Output flow is as expected.") {
}
}
WHEN("bridge_flow_ratio is set to 2.0") {
THEN("Output flow is as expected.") {
}
}
}
GIVEN("A default config with no cooling and a fixed bridge speed, flow ratio, fixed extrusion width of 0.4mm and an overhang mesh.") {
WHEN("bridge_flow_ratio is set to 1.0") {
THEN("Output flow is as expected.") {
}
}
WHEN("bridge_flow_ratio is set to 0.5") {
THEN("Output flow is as expected.") {
}
}
WHEN("bridge_flow_ratio is set to 2.0") {
THEN("Output flow is as expected.") {
}
}
}
}
/// Test the expected behavior for auto-width,
/// spacing, etc
SCENARIO("Flow: Flow math for non-bridges", "[!mayfail]") {
GIVEN("Nozzle Diameter of 0.4, a desired width of 1mm and layer height of 0.5") {
auto width {ConfigOptionFloatOrPercent(1.0, false)};
float spacing {0.4f};
float nozzle_diameter {0.4f};
float bridge_flow {1.0f};
float layer_height {0.5f};
// Spacing for non-bridges is has some overlap
THEN("External perimeter flow has a default spacing fixed to 1.05*nozzle_diameter") {
Flow flow {Flow::new_from_config_width(frExternalPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height, 0.0f)};
REQUIRE(flow.spacing() == Approx((1.05f*nozzle_diameter) - layer_height * (1.0 - PI / 4.0)));
}
THEN("Internal perimeter flow has a default spacing fixed to 1.125*nozzle_diameter") {
Flow flow {Flow::new_from_config_width(frPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height, 0.0f)};
REQUIRE(flow.spacing() == Approx((1.125*nozzle_diameter) - layer_height * (1.0 - PI / 4.0)));
}
THEN("Spacing for supplied width is 0.8927f") {
Flow flow {Flow::new_from_config_width(frExternalPerimeter, width, nozzle_diameter, layer_height, 0.0f)};
REQUIRE(flow.spacing() == Approx(width - layer_height * (1.0 - PI / 4.0)));
flow = Flow::new_from_config_width(frPerimeter, width, nozzle_diameter, layer_height, 0.0f);
REQUIRE(flow.spacing() == Approx(width - layer_height * (1.0 - PI / 4.0)));
}
}
/// Check the min/max
GIVEN("Nozzle Diameter of 0.25 with extreme width") {
float nozzle_diameter {0.25f};
float layer_height {0.5f};
WHEN("layer height is set to 0.15") {
layer_height = 5.f;
THEN("Max width is respected.") {
auto flow {Flow::new_from_config_width(frPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height, 0.0f)};
REQUIRE(flow.width <= Approx(1.4*nozzle_diameter));
}
THEN("Min width is respected") {
auto flow{ Flow::new_from_config_width(frPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height, 0.0f) };
REQUIRE(flow.width >= Approx(1.05*nozzle_diameter));
}
}
WHEN("Layer height is set to 0.3") {
layer_height = 0.01f;
THEN("Max width is respected.") {
auto flow{ Flow::new_from_config_width(frPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height, 0.0f) };
REQUIRE(flow.width <= Approx(1.4*nozzle_diameter));
}
THEN("Min width is respected.") {
auto flow{ Flow::new_from_config_width(frPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height, 0.0f) };
REQUIRE(flow.width >= Approx(1.05*nozzle_diameter));
}
}
}
///// Check for an edge case in the maths where the spacing could be 0; original
///// math is 0.99. Slic3r issue #4654
//GIVEN("Input spacing of 0.414159 and a total width of 2") {
// double in_spacing = 0.414159;
// double total_width = 2.0;
// auto flow {Flow::new_from_spacing(1.0, 0.4, 0.3, false)};
// WHEN("solid_spacing() is called") {
// double result = flow.solid_spacing(total_width, in_spacing);
// THEN("Yielded spacing is greater than 0") {
// REQUIRE(result > 0);
// }
// }
//}
}
/// Spacing, width calculation for bridge extrusions
SCENARIO("Flow: Flow math for bridges", "[!mayfail]") {
GIVEN("Nozzle Diameter of 0.4, a desired width of 1mm and layer height of 0.5") {
auto width {ConfigOptionFloatOrPercent(1.0, false)};
auto spacing {0.4};
auto nozzle_diameter {0.4};
auto bridge_flow {1.0};
auto layer_height {0.5};
WHEN("Flow role is frExternalPerimeter") {
auto flow {Flow::new_from_config_width(frExternalPerimeter, width, nozzle_diameter, layer_height, bridge_flow)};
THEN("Bridge width is same as nozzle diameter") {
REQUIRE(flow.width == Approx(nozzle_diameter));
}
THEN("Bridge spacing is same as nozzle diameter + BRIDGE_EXTRA_SPACING") {
REQUIRE(flow.spacing() == Approx(nozzle_diameter + BRIDGE_EXTRA_SPACING));
}
}
WHEN("Flow role is frInfill") {
auto flow {Flow::new_from_config_width(frInfill, width, nozzle_diameter, layer_height, bridge_flow)};
THEN("Bridge width is same as nozzle diameter") {
REQUIRE(flow.width == Approx(nozzle_diameter));
}
THEN("Bridge spacing is same as nozzle diameter + BRIDGE_EXTRA_SPACING") {
REQUIRE(flow.spacing() == Approx(nozzle_diameter + BRIDGE_EXTRA_SPACING));
}
}
WHEN("Flow role is frPerimeter") {
auto flow {Flow::new_from_config_width(frPerimeter, width, nozzle_diameter, layer_height, bridge_flow)};
THEN("Bridge width is same as nozzle diameter") {
REQUIRE(flow.width == Approx(nozzle_diameter));
}
THEN("Bridge spacing is same as nozzle diameter + BRIDGE_EXTRA_SPACING") {
REQUIRE(flow.spacing() == Approx(nozzle_diameter + BRIDGE_EXTRA_SPACING));
}
}
WHEN("Flow role is frSupportMaterial") {
auto flow {Flow::new_from_config_width(frSupportMaterial, width, nozzle_diameter, layer_height, bridge_flow)};
THEN("Bridge width is same as nozzle diameter") {
REQUIRE(flow.width == Approx(nozzle_diameter));
}
THEN("Bridge spacing is same as nozzle diameter + BRIDGE_EXTRA_SPACING") {
REQUIRE(flow.spacing() == Approx(nozzle_diameter + BRIDGE_EXTRA_SPACING));
}
}
}
}

View File

@ -0,0 +1,100 @@
#include <catch.hpp>
#include <memory>
#include "GCodeWriter.hpp"
#include "test_options.hpp"
using namespace Slic3r;
using namespace std::literals::string_literals;
SCENARIO("lift() and unlift() behavior with large values of Z", "[!shouldfail]") {
GIVEN("A config from a file and a single extruder.") {
GCodeWriter writer;
auto& config {writer.config};
config.set_defaults();
config.load(std::string(testfile_dir) + "test_gcodewriter/config_lift_unlift.ini"s);
std::vector<unsigned int> extruder_ids {0};
writer.set_extruders(extruder_ids);
writer.set_extruder(0);
WHEN("Z is set to 9007199254740992") {
double trouble_Z = 9007199254740992;
writer.travel_to_z(trouble_Z);
AND_WHEN("GcodeWriter::Lift() is called") {
REQUIRE(writer.lift().size() > 0);
AND_WHEN("Z is moved post-lift to the same delta as the config Z lift") {
REQUIRE(writer.travel_to_z(trouble_Z + config.retract_lift.values[0]).size() == 0);
AND_WHEN("GCodeWriter::Unlift() is called") {
REQUIRE(writer.unlift().size() == 0); // we're the same height so no additional move happens.
THEN("GCodeWriter::Lift() emits gcode.") {
REQUIRE(writer.lift().size() > 0);
}
}
}
}
}
}
}
SCENARIO("lift() is not ignored after unlift() at normal values of Z") {
GIVEN("A config from a file and a single extruder.") {
GCodeWriter writer;
auto& config {writer.config};
config.set_defaults();
config.load(std::string(testfile_dir) + "test_gcodewriter/config_lift_unlift.ini"s);
std::vector<unsigned int> extruder_ids {0};
writer.set_extruders(extruder_ids);
writer.set_extruder(0);
WHEN("Z is set to 203") {
double trouble_Z = 203;
writer.travel_to_z(trouble_Z);
AND_WHEN("GcodeWriter::Lift() is called") {
REQUIRE(writer.lift().size() > 0);
AND_WHEN("Z is moved post-lift to the same delta as the config Z lift") {
REQUIRE(writer.travel_to_z(trouble_Z + config.retract_lift.values[0]).size() == 0);
AND_WHEN("GCodeWriter::Unlift() is called") {
REQUIRE(writer.unlift().size() == 0); // we're the same height so no additional move happens.
THEN("GCodeWriter::Lift() emits gcode.") {
REQUIRE(writer.lift().size() > 0);
}
}
}
}
}
WHEN("Z is set to 500003") {
double trouble_Z = 500003;
writer.travel_to_z(trouble_Z);
AND_WHEN("GcodeWriter::Lift() is called") {
REQUIRE(writer.lift().size() > 0);
AND_WHEN("Z is moved post-lift to the same delta as the config Z lift") {
REQUIRE(writer.travel_to_z(trouble_Z + config.retract_lift.values[0]).size() == 0);
AND_WHEN("GCodeWriter::Unlift() is called") {
REQUIRE(writer.unlift().size() == 0); // we're the same height so no additional move happens.
THEN("GCodeWriter::Lift() emits gcode.") {
REQUIRE(writer.lift().size() > 0);
}
}
}
}
}
WHEN("Z is set to 10.3") {
double trouble_Z = 10.3;
writer.travel_to_z(trouble_Z);
AND_WHEN("GcodeWriter::Lift() is called") {
REQUIRE(writer.lift().size() > 0);
AND_WHEN("Z is moved post-lift to the same delta as the config Z lift") {
REQUIRE(writer.travel_to_z(trouble_Z + config.retract_lift.values[0]).size() == 0);
AND_WHEN("GCodeWriter::Unlift() is called") {
REQUIRE(writer.unlift().size() == 0); // we're the same height so no additional move happens.
THEN("GCodeWriter::Lift() emits gcode.") {
REQUIRE(writer.lift().size() > 0);
}
}
}
}
}
}
}

View File

@ -0,0 +1,378 @@
#include <catch.hpp>
#include "Point.hpp"
#include "BoundingBox.hpp"
#include "Polygon.hpp"
#include "Polyline.hpp"
#include "Line.hpp"
#include "Geometry.hpp"
#include "ClipperUtils.hpp"
using namespace Slic3r;
TEST_CASE("Polygon::contains works properly", ""){
// this test was failing on Windows (GH #1950)
auto polygon = Polygon(std::vector<Point>({
Point(207802834,-57084522),
Point(196528149,-37556190),
Point(173626821,-25420928),
Point(171285751,-21366123),
Point(118673592,-21366123),
Point(116332562,-25420928),
Point(93431208,-37556191),
Point(82156517,-57084523),
Point(129714478,-84542120),
Point(160244873,-84542120)
}));
auto point = Point(95706562, -57294774);
REQUIRE(polygon.contains(point));
}
SCENARIO("Intersections of line segments"){
GIVEN("Integer coordinates"){
auto line1 = Line(Point(5,15),Point(30,15));
auto line2 = Line(Point(10,20), Point(10,10));
THEN("The intersection is valid"){
Point point;
line1.intersection(line2,&point);
REQUIRE(Point(10,15) == point);
}
}
GIVEN("Scaled coordinates"){
auto line1 = Line(Point(73.6310778185108/0.0000001, 371.74239268924/0.0000001), Point(73.6310778185108/0.0000001, 501.74239268924/0.0000001));
auto line2 = Line(Point(75/0.0000001, 437.9853/0.0000001), Point(62.7484/0.0000001, 440.4223/0.0000001));
THEN("There is still an intersection"){
Point point;
REQUIRE(line1.intersection(line2,&point));
}
}
}
/*
Tests for unused methods still written in perl
{
my $polygon = Slic3r::Polygon->new(
[45919000, 515273900], [14726100, 461246400], [14726100, 348753500], [33988700, 315389800],
[43749700, 343843000], [45422300, 352251500], [52362100, 362637800], [62748400, 369577600],
[75000000, 372014700], [87251500, 369577600], [97637800, 362637800], [104577600, 352251500],
[107014700, 340000000], [104577600, 327748400], [97637800, 317362100], [87251500, 310422300],
[82789200, 309534700], [69846100, 294726100], [254081000, 294726100], [285273900, 348753500],
[285273900, 461246400], [254081000, 515273900],
);
# this points belongs to $polyline
# note: it's actually a vertex, while we should better check an intermediate point
my $point = Slic3r::Point->new(104577600, 327748400);
local $Slic3r::Geometry::epsilon = 1E-5;
is_deeply Slic3r::Geometry::polygon_segment_having_point($polygon, $point)->pp,
[ [107014700, 340000000], [104577600, 327748400] ],
'polygon_segment_having_point';
}
{
auto point = Point(736310778.185108, 5017423926.8924);
auto line = Line(Point((long int) 627484000, (long int) 3695776000), Point((long int) 750000000, (long int)3720147000));
//is Slic3r::Geometry::point_in_segment($point, $line), 0, 'point_in_segment';
}
// Possible to delete
{
//my $p1 = [10, 10];
//my $p2 = [10, 20];
//my $p3 = [10, 30];
//my $p4 = [20, 20];
//my $p5 = [0, 20];
THEN("Points in a line give the correct angles"){
//is Slic3r::Geometry::angle3points($p2, $p3, $p1), PI(), 'angle3points';
//is Slic3r::Geometry::angle3points($p2, $p1, $p3), PI(), 'angle3points';
}
THEN("Left turns give the correct angle"){
//is Slic3r::Geometry::angle3points($p2, $p4, $p3), PI()/2, 'angle3points';
//is Slic3r::Geometry::angle3points($p2, $p1, $p4), PI()/2, 'angle3points';
}
THEN("Right turns give the correct angle"){
//is Slic3r::Geometry::angle3points($p2, $p3, $p4), PI()/2*3, 'angle3points';
//is Slic3r::Geometry::angle3points($p2, $p1, $p5), PI()/2*3, 'angle3points';
}
//my $p1 = [30, 30];
//my $p2 = [20, 20];
//my $p3 = [10, 10];
//my $p4 = [30, 10];
//is Slic3r::Geometry::angle3points($p2, $p1, $p3), PI(), 'angle3points';
//is Slic3r::Geometry::angle3points($p2, $p1, $p4), PI()/2*3, 'angle3points';
//is Slic3r::Geometry::angle3points($p2, $p1, $p1), 2*PI(), 'angle3points';
}
SCENARIO("polygon_is_convex works"){
GIVEN("A square of dimension 10"){
//my $cw_square = [ [0,0], [0,10], [10,10], [10,0] ];
THEN("It is not convex clockwise"){
//is polygon_is_convex($cw_square), 0, 'cw square is not convex';
}
THEN("It is convex counter-clockwise"){
//is polygon_is_convex([ reverse @$cw_square ]), 1, 'ccw square is convex';
}
}
GIVEN("A concave polygon"){
//my $convex1 = [ [0,0], [10,0], [10,10], [0,10], [0,6], [4,6], [4,4], [0,4] ];
THEN("It is concave"){
//is polygon_is_convex($convex1), 0, 'concave polygon';
}
}
}*/
TEST_CASE("Creating a polyline generates the obvious lines"){
auto polyline = Polyline();
polyline.points = std::vector<Point>({Point(0, 0), Point(10, 0), Point(20, 0)});
REQUIRE(polyline.lines().at(0).a == Point(0,0));
REQUIRE(polyline.lines().at(0).b == Point(10,0));
REQUIRE(polyline.lines().at(1).a == Point(10,0));
REQUIRE(polyline.lines().at(1).b == Point(20,0));
}
TEST_CASE("Splitting a Polygon generates a polyline correctly"){
auto polygon = Polygon(std::vector<Point>({Point(0, 0), Point(10, 0), Point(5, 5)}));
auto split = polygon.split_at_index(1);
REQUIRE(split.points[0]==Point(10,0));
REQUIRE(split.points[1]==Point(5,5));
REQUIRE(split.points[2]==Point(0,0));
REQUIRE(split.points[3]==Point(10,0));
}
TEST_CASE("Bounding boxes are scaled appropriately"){
auto bb = BoundingBox(std::vector<Point>({Point(0, 1), Point(10, 2), Point(20, 2)}));
bb.scale(2);
REQUIRE(bb.min == Point(0,2));
REQUIRE(bb.max == Point(40,4));
}
TEST_CASE("Offseting a line generates a polygon correctly"){
auto line = Line(Point(10,10), Point(20,10));
Polyline tmp(line);
Polygon area = offset(tmp,5).at(0);
REQUIRE(area.area() == Polygon(std::vector<Point>({Point(10,5),Point(20,5),Point(20,15),Point(10,15)})).area());
}
SCENARIO("Circle Fit, TaubinFit with Newton's method") {
GIVEN("A vector of Pointfs arranged in a half-circle with approximately the same distance R from some point") {
Pointf expected_center(-6, 0);
Pointfs sample {Pointf(6.0, 0), Pointf(5.1961524, 3), Pointf(3 ,5.1961524), Pointf(0, 6.0), Pointf(3, 5.1961524), Pointf(-5.1961524, 3), Pointf(-6.0, 0)};
std::transform(sample.begin(), sample.end(), sample.begin(), [expected_center] (const Pointf& a) { return a + expected_center;});
WHEN("Circle fit is called on the entire array") {
Pointf result_center(0,0);
result_center = Geometry::circle_taubin_newton(sample);
THEN("A center point of -6,0 is returned.") {
REQUIRE(result_center == expected_center);
}
}
WHEN("Circle fit is called on the first four points") {
Pointf result_center(0,0);
result_center = Geometry::circle_taubin_newton(sample.cbegin(), sample.cbegin()+4);
THEN("A center point of -6,0 is returned.") {
REQUIRE(result_center == expected_center);
}
}
WHEN("Circle fit is called on the middle four points") {
Pointf result_center(0,0);
result_center = Geometry::circle_taubin_newton(sample.cbegin()+2, sample.cbegin()+6);
THEN("A center point of -6,0 is returned.") {
REQUIRE(result_center == expected_center);
}
}
}
GIVEN("A vector of Pointfs arranged in a half-circle with approximately the same distance R from some point") {
Pointf expected_center(-3, 9);
Pointfs sample {Pointf(6.0, 0), Pointf(5.1961524, 3), Pointf(3 ,5.1961524),
Pointf(0, 6.0),
Pointf(3, 5.1961524), Pointf(-5.1961524, 3), Pointf(-6.0, 0)};
std::transform(sample.begin(), sample.end(), sample.begin(), [expected_center] (const Pointf& a) { return a + expected_center;});
WHEN("Circle fit is called on the entire array") {
Pointf result_center(0,0);
result_center = Geometry::circle_taubin_newton(sample);
THEN("A center point of 3,9 is returned.") {
REQUIRE(result_center == expected_center);
}
}
WHEN("Circle fit is called on the first four points") {
Pointf result_center(0,0);
result_center = Geometry::circle_taubin_newton(sample.cbegin(), sample.cbegin()+4);
THEN("A center point of 3,9 is returned.") {
REQUIRE(result_center == expected_center);
}
}
WHEN("Circle fit is called on the middle four points") {
Pointf result_center(0,0);
result_center = Geometry::circle_taubin_newton(sample.cbegin()+2, sample.cbegin()+6);
THEN("A center point of 3,9 is returned.") {
REQUIRE(result_center == expected_center);
}
}
}
GIVEN("A vector of Points arranged in a half-circle with approximately the same distance R from some point") {
Point expected_center { Point::new_scale(-3, 9)};
Points sample {Point::new_scale(6.0, 0), Point::new_scale(5.1961524, 3), Point::new_scale(3 ,5.1961524),
Point::new_scale(0, 6.0),
Point::new_scale(3, 5.1961524), Point::new_scale(-5.1961524, 3), Point::new_scale(-6.0, 0)};
std::transform(sample.begin(), sample.end(), sample.begin(), [expected_center] (const Point& a) { return a + expected_center;});
WHEN("Circle fit is called on the entire array") {
Point result_center(0,0);
result_center = Geometry::circle_taubin_newton(sample);
THEN("A center point of scaled 3,9 is returned.") {
REQUIRE(result_center.coincides_with_epsilon(expected_center));
}
}
WHEN("Circle fit is called on the first four points") {
Point result_center(0,0);
result_center = Geometry::circle_taubin_newton(sample.cbegin(), sample.cbegin()+4);
THEN("A center point of scaled 3,9 is returned.") {
REQUIRE(result_center.coincides_with_epsilon(expected_center));
}
}
WHEN("Circle fit is called on the middle four points") {
Point result_center(0,0);
result_center = Geometry::circle_taubin_newton(sample.cbegin()+2, sample.cbegin()+6);
THEN("A center point of scaled 3,9 is returned.") {
REQUIRE(result_center.coincides_with_epsilon(expected_center));
}
}
}
}
TEST_CASE("Chained path working correctly"){
// if chained_path() works correctly, these points should be joined with no diagonal paths
// (thus 26 units long)
std::vector<Point> points = {Point(26,26),Point(52,26),Point(0,26),Point(26,52),Point(26,0),Point(0,52),Point(52,52),Point(52,0)};
std::vector<Points::size_type> indices;
Geometry::chained_path(points,indices);
for(Points::size_type i = 0; i < indices.size()-1;i++){
double dist = points.at(indices.at(i)).distance_to(points.at(indices.at(i+1)));
REQUIRE(abs(dist-26) <= Geometry::epsilon);
}
}
SCENARIO("Line distances"){
GIVEN("A line"){
auto line = Line(Point(0, 0), Point(20, 0));
THEN("Points on the line segment have 0 distance"){
REQUIRE(Point(0, 0).distance_to(line) == 0);
REQUIRE(Point(20, 0).distance_to(line) == 0);
REQUIRE(Point(10, 0).distance_to(line) == 0);
}
THEN("Points off the line have the appropriate distance"){
REQUIRE(Point(10, 10).distance_to(line) == 10);
REQUIRE(Point(50, 0).distance_to(line) == 30);
}
}
}
SCENARIO("Polygon convex/concave detection"){
GIVEN(("A Square with dimension 100")){
auto square = Polygon /*new_scale*/(std::vector<Point>({
Point(100,100),
Point(200,100),
Point(200,200),
Point(100,200)}));
THEN("It has 4 convex points counterclockwise"){
REQUIRE(square.concave_points(PI*4/3).size() == 0);
REQUIRE(square.convex_points(PI*2/3).size() == 4);
}
THEN("It has 4 concave points clockwise"){
square.make_clockwise();
REQUIRE(square.concave_points(PI*4/3).size() == 4);
REQUIRE(square.convex_points(PI*2/3).size() == 0);
}
}
GIVEN("A Square with an extra colinearvertex"){
auto square = Polygon /*new_scale*/(std::vector<Point>({
Point(150,100),
Point(200,100),
Point(200,200),
Point(100,200),
Point(100,100)}));
THEN("It has 4 convex points counterclockwise"){
REQUIRE(square.concave_points(PI*4/3).size() == 0);
REQUIRE(square.convex_points(PI*2/3).size() == 4);
}
}
GIVEN("A Square with an extra collinear vertex in different order"){
auto square = Polygon /*new_scale*/(std::vector<Point>({
Point(200,200),
Point(100,200),
Point(100,100),
Point(150,100),
Point(200,100)}));
THEN("It has 4 convex points counterclockwise"){
REQUIRE(square.concave_points(PI*4/3).size() == 0);
REQUIRE(square.convex_points(PI*2/3).size() == 4);
}
}
GIVEN("A triangle"){
auto triangle = Polygon(std::vector<Point>({
Point(16000170,26257364),
Point(714223,461012),
Point(31286371,461008)
}));
THEN("it has three convex vertices"){
REQUIRE(triangle.concave_points(PI*4/3).size() == 0);
REQUIRE(triangle.convex_points(PI*2/3).size() == 3);
}
}
GIVEN("A triangle with an extra collinear point"){
auto triangle = Polygon(std::vector<Point>({
Point(16000170,26257364),
Point(714223,461012),
Point(20000000,461012),
Point(31286371,461012)
}));
THEN("it has three convex vertices"){
REQUIRE(triangle.concave_points(PI*4/3).size() == 0);
REQUIRE(triangle.convex_points(PI*2/3).size() == 3);
}
}
GIVEN("A polygon with concave vertices with angles of specifically 4/3pi"){
// Two concave vertices of this polygon have angle = PI*4/3, so this test fails
// if epsilon is not used.
auto polygon = Polygon(std::vector<Point>({
Point(60246458,14802768),Point(64477191,12360001),
Point(63727343,11060995),Point(64086449,10853608),
Point(66393722,14850069),Point(66034704,15057334),
Point(65284646,13758387),Point(61053864,16200839),
Point(69200258,30310849),Point(62172547,42483120),
Point(61137680,41850279),Point(67799985,30310848),
Point(51399866,1905506),Point(38092663,1905506),
Point(38092663,692699),Point(52100125,692699)
}));
THEN("the correct number of points are detected"){
REQUIRE(polygon.concave_points(PI*4/3).size() == 6);
REQUIRE(polygon.convex_points(PI*2/3).size() == 10);
}
}
}
TEST_CASE("Triangle Simplification does not result in less than 3 points"){
auto triangle = Polygon(std::vector<Point>({
Point(16000170,26257364), Point(714223,461012), Point(31286371,461008)
}));
REQUIRE(triangle.simplify(250000).at(0).points.size() == 3);
}

View File

@ -0,0 +1,695 @@
#include <catch.hpp>
#include <test_options.hpp>
#include "Log.hpp"
using namespace std::literals::string_literals;
using namespace Slic3r;
SCENARIO( "_Log output with std::string methods" ) {
GIVEN("A log stream and a _Log object") {
std::stringstream log;
std::unique_ptr<_Log> cut { _Log::make_log(log) };
cut->set_level(log_t::DEBUG);
cut->set_inclusive(true);
WHEN("fatal_error is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->fatal_error("Topic", "This");
THEN("Output string is Topic FERR: This\\n") {
REQUIRE(log.str() == "Topic FERR: This\n");
}
}
WHEN("error is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->error("Topic", "This");
THEN("Output string is Topic ERR: This\\n") {
REQUIRE(log.str() == "Topic ERR: This\n");
}
}
WHEN("info is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->info("Topic", "This");
THEN("Output string is Topic INFO: This\\n") {
REQUIRE(log.str() == "Topic INFO: This\n");
}
}
WHEN("warn is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->warn("Topic", "This");
THEN("Output string is Topic WARN: This\\n") {
REQUIRE(log.str() == "Topic WARN: This\n");
}
}
WHEN("info is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->info("Topic", "This");
THEN("Output string is Topic INFO: This\\n") {
REQUIRE(log.str() == "Topic INFO: This\n");
}
}
WHEN("debug is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->debug("Topic", "This");
THEN("Output string is Topic DEBUG: This\\n") {
REQUIRE(log.str() == "Topic DEBUG: This\n");
}
}
WHEN("msg is called with text \"This\"") {
log.clear();
cut->raw("This");
THEN("Output string is This\\n") {
REQUIRE(log.str() == "This\n");
}
}
}
}
SCENARIO( "_Log output with std::wstring methods" ) {
GIVEN("A log stream and a _Log object") {
std::stringstream log;
std::unique_ptr<_Log> cut { _Log::make_log(log) };
cut->set_level(log_t::DEBUG);
cut->set_inclusive(true);
WHEN("fatal_error is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->fatal_error("Topic", L"This");
THEN("Output string is Topic FERR: This\\n") {
REQUIRE(log.str() == "Topic FERR: This\n");
}
}
WHEN("error is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->error("Topic", L"This");
THEN("Output string is Topic ERR: This\\n") {
REQUIRE(log.str() == "Topic ERR: This\n");
}
}
WHEN("info is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->info("Topic", L"This");
THEN("Output string is Topic INFO: This\\n") {
REQUIRE(log.str() == "Topic INFO: This\n");
}
}
WHEN("warn is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->warn("Topic", L"This");
THEN("Output string is Topic WARN: This\\n") {
REQUIRE(log.str() == "Topic WARN: This\n");
}
}
WHEN("info is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->info("Topic", L"This");
THEN("Output string is Topic INFO: This\\n") {
REQUIRE(log.str() == "Topic INFO: This\n");
}
}
WHEN("debug is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->debug("Topic", L"This");
THEN("Output string is Topic DEBUG: This\\n") {
REQUIRE(log.str() == "Topic DEBUG: This\n");
}
}
WHEN("msg is called with text \"This\"") {
log.clear();
cut->raw(L"This");
THEN("Output string is This\\n") {
REQUIRE(log.str() == "This\n");
}
}
}
}
SCENARIO( "_Log output with << methods" ) {
GIVEN("A log stream and a _Log object") {
std::stringstream log;
std::unique_ptr<_Log> cut { _Log::make_log(log) };
cut->set_level(log_t::DEBUG);
cut->set_inclusive(true);
WHEN("fatal_error is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->fatal_error("Topic") << "This";
THEN("Output string is Topic FERR: This") {
REQUIRE(log.str() == "Topic FERR: This");
}
}
WHEN("error is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->error("Topic") << "This";
THEN("Output string is Topic ERR: This") {
REQUIRE(log.str() == "Topic ERR: This");
}
}
WHEN("info is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->info("Topic") << "This";
THEN("Output string is Topic INFO: This") {
REQUIRE(log.str() == "Topic INFO: This");
}
}
WHEN("warn is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->warn("Topic") << "This";
THEN("Output string is Topic WARN: This") {
REQUIRE(log.str() == "Topic WARN: This");
}
}
WHEN("info is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->info("Topic") << "This";
THEN("Output string is Topic INFO: This") {
REQUIRE(log.str() == "Topic INFO: This");
}
}
WHEN("debug is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->debug("Topic") << "This";
THEN("Output string is Topic DEBUG: This") {
REQUIRE(log.str() == "Topic DEBUG: This");
}
}
WHEN("msg is called with text \"This\"") {
log.clear();
cut->raw() << "This";
THEN("Output string is This") {
REQUIRE(log.str() == "This");
}
}
}
}
SCENARIO( "_Log output inclusive filtering with std::string methods" ) {
GIVEN("Single, inclusive log level of FERR (highest)") {
std::stringstream log;
std::unique_ptr<_Log> cut { _Log::make_log(log) };
cut->clear_level(log_t::FERR);
cut->set_inclusive(true);
cut->set_level(log_t::FERR);
WHEN("fatal_error is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->fatal_error("Topic", "This");
THEN("Output string is Topic FERR: This\\n") {
REQUIRE(log.str() == "Topic FERR: This\n");
}
}
WHEN("error is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->error("Topic", "This");
THEN("Output string is blank") {
REQUIRE(log.str() == "");
}
}
WHEN("warn is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->warn("Topic", "This");
THEN("Output string is blank") {
REQUIRE(log.str() == "");
}
}
WHEN("info is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->info("Topic", "This");
THEN("Output string is blank") {
REQUIRE(log.str() == "");
}
}
WHEN("debug is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->debug("Topic", "This");
THEN("Output string is blank") {
REQUIRE(log.str() == "");
}
}
}
GIVEN("Single, inclusive log level of ERR (second-highest)") {
std::stringstream log;
std::unique_ptr<_Log> cut { _Log::make_log(log) };
cut->set_inclusive(true);
cut->set_level(log_t::ERR);
WHEN("fatal_error is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->fatal_error("Topic", "This");
THEN("Output string is Topic FERR: This\\n") {
REQUIRE(log.str() == "Topic FERR: This\n");
}
}
WHEN("error is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->error("Topic", "This");
THEN("Output string is Topic ERR: This\\n") {
REQUIRE(log.str() == "Topic ERR: This\n");
}
}
WHEN("warn is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->warn("Topic", "This");
THEN("Output string is blank") {
REQUIRE(log.str() == "");
}
}
WHEN("info is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->info("Topic", "This");
THEN("Output string is blank") {
REQUIRE(log.str() == "");
}
}
WHEN("debug is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->debug("Topic", "This");
THEN("Output string is blank") {
REQUIRE(log.str() == "");
}
}
}
GIVEN("Single, inclusive log level of WARN (third-highest)") {
std::stringstream log;
std::unique_ptr<_Log> cut { _Log::make_log(log) };
cut->set_inclusive(true);
cut->set_level(log_t::WARN);
WHEN("fatal_error is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->fatal_error("Topic", "This");
THEN("Output string is Topic FERR: This\\n") {
REQUIRE(log.str() == "Topic FERR: This\n");
}
}
WHEN("error is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->error("Topic", "This");
THEN("Output string is Topic ERR: This\\n") {
REQUIRE(log.str() == "Topic ERR: This\n");
}
}
WHEN("warn is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->warn("Topic", "This");
THEN("Output string is Topic WARN: This\\n") {
REQUIRE(log.str() == "Topic WARN: This\n");
}
}
WHEN("info is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->info("Topic", "This");
THEN("Output string is blank") {
REQUIRE(log.str() == "");
}
}
WHEN("debug is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->debug("Topic", "This");
THEN("Output string is blank") {
REQUIRE(log.str() == "");
}
}
}
GIVEN("Single, inclusive log level of INFO (fourth-highest)") {
std::stringstream log;
std::unique_ptr<_Log> cut { _Log::make_log(log) };
cut->set_inclusive(true);
cut->set_level(log_t::INFO);
WHEN("fatal_error is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->fatal_error("Topic", "This");
THEN("Output string is Topic FERR: This\\n") {
REQUIRE(log.str() == "Topic FERR: This\n");
}
}
WHEN("error is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->error("Topic", "This");
THEN("Output string is Topic ERR: This\\n") {
REQUIRE(log.str() == "Topic ERR: This\n");
}
}
WHEN("warn is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->warn("Topic", "This");
THEN("Output string is Topic WARN: This\\n") {
REQUIRE(log.str() == "Topic WARN: This\n");
}
}
WHEN("info is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->info("Topic", "This");
THEN("Output string is Topic INFO: This\\n") {
REQUIRE(log.str() == "Topic INFO: This\n");
}
}
WHEN("debug is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->debug("Topic", "This");
THEN("Output string is blank") {
REQUIRE(log.str() == "");
}
}
}
GIVEN("Single, inclusive log level of DEBUG (fifth-highest)") {
std::stringstream log;
std::unique_ptr<_Log> cut { _Log::make_log(log) };
cut->set_inclusive(true);
cut->set_level(log_t::DEBUG);
WHEN("fatal_error is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->fatal_error("Topic", "This");
THEN("Output string is Topic FERR: This\\n") {
REQUIRE(log.str() == "Topic FERR: This\n");
}
}
WHEN("error is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->error("Topic", "This");
THEN("Output string is Topic ERR: This\\n") {
REQUIRE(log.str() == "Topic ERR: This\n");
}
}
WHEN("warn is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->warn("Topic", "This");
THEN("Output string is Topic WARN: This\\n") {
REQUIRE(log.str() == "Topic WARN: This\n");
}
}
WHEN("info is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->info("Topic", "This");
THEN("Output string is Topic INFO: This\\n") {
REQUIRE(log.str() == "Topic INFO: This\n");
}
}
WHEN("debug is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->debug("Topic", "This");
THEN("Output string is Topic DEBUG: This\\n") {
REQUIRE(log.str() == "Topic DEBUG: This\n");
}
}
}
}
SCENARIO( "_Log output set filtering with std::string methods" ) {
GIVEN("log level of DEBUG only") {
std::stringstream log;
std::unique_ptr<_Log> cut { _Log::make_log(log) };
cut->set_inclusive(false);
cut->clear_level(log_t::ALL);
cut->set_level(log_t::DEBUG);
WHEN("fatal_error is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->fatal_error("Topic", "This");
THEN("Output string is blank") {
REQUIRE(log.str() == "");
}
}
WHEN("error is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->error("Topic", "This");
THEN("Output string is blank") {
REQUIRE(log.str() == "");
}
}
WHEN("warn is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->warn("Topic", "This");
THEN("Output string is blank") {
REQUIRE(log.str() == "");
}
}
WHEN("info is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->info("Topic", "This");
THEN("Output string is blank") {
REQUIRE(log.str() == "");
}
}
WHEN("debug is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->debug("Topic", "This");
THEN("Output string is Topic DEBUG: This\\n") {
REQUIRE(log.str() == "Topic DEBUG: This\n");
}
}
}
GIVEN("log level of INFO only") {
std::stringstream log;
std::unique_ptr<_Log> cut { _Log::make_log(log) };
cut->set_inclusive(false);
cut->clear_level(log_t::ALL);
cut->set_level(log_t::INFO);
WHEN("fatal_error is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->fatal_error("Topic", "This");
THEN("Output string is blank") {
REQUIRE(log.str() == "");
}
}
WHEN("error is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->error("Topic", "This");
THEN("Output string is blank") {
REQUIRE(log.str() == "");
}
}
WHEN("warn is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->warn("Topic", "This");
THEN("Output string is blank") {
REQUIRE(log.str() == "");
}
}
WHEN("info is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->info("Topic", "This");
THEN("Output string is Topic INFO: This\\n") {
REQUIRE(log.str() == "Topic INFO: This\n");
}
}
WHEN("debug is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->debug("Topic", "This");
THEN("Output string is blank") {
REQUIRE(log.str() == "");
}
}
}
GIVEN("log level of WARN only") {
std::stringstream log;
std::unique_ptr<_Log> cut { _Log::make_log(log) };
cut->set_inclusive(false);
cut->clear_level(log_t::ALL);
cut->set_level(log_t::WARN);
WHEN("fatal_error is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->fatal_error("Topic", "This");
THEN("Output string is blank") {
REQUIRE(log.str() == "");
}
}
WHEN("error is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->error("Topic", "This");
THEN("Output string is blank") {
REQUIRE(log.str() == "");
}
}
WHEN("warn is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->warn("Topic", "This");
THEN("Output string is Topic WARN: This\\n") {
REQUIRE(log.str() == "Topic WARN: This\n");
}
}
WHEN("info is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->info("Topic", "This");
THEN("Output string is blank") {
REQUIRE(log.str() == "");
}
}
WHEN("debug is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->debug("Topic", "This");
THEN("Output string is blank") {
REQUIRE(log.str() == "");
}
}
}
GIVEN("log level of FERR only") {
std::stringstream log;
std::unique_ptr<_Log> cut { _Log::make_log(log) };
cut->set_inclusive(false);
cut->clear_level(log_t::ALL);
cut->set_level(log_t::FERR);
WHEN("fatal_error is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->fatal_error("Topic", "This");
THEN("Output string is Topic FERR: This\\n") {
REQUIRE(log.str() == "Topic FERR: This\n");
}
}
WHEN("error is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->error("Topic", "This");
THEN("Output string is blank") {
REQUIRE(log.str() == "");
}
}
WHEN("warn is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->warn("Topic", "This");
THEN("Output string is blank") {
REQUIRE(log.str() == "");
}
}
WHEN("info is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->info("Topic", "This");
THEN("Output string is blank") {
REQUIRE(log.str() == "");
}
}
WHEN("debug is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->debug("Topic", "This");
THEN("Output string is blank") {
REQUIRE(log.str() == "");
}
}
}
GIVEN("log level of DEBUG and ERR") {
std::stringstream log;
std::unique_ptr<_Log> cut { _Log::make_log(log) };
cut->set_inclusive(false);
cut->clear_level(log_t::ALL);
cut->set_level(log_t::DEBUG);
cut->set_level(log_t::ERR);
WHEN("fatal_error is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->fatal_error("Topic", "This");
THEN("Output string is blank") {
REQUIRE(log.str() == "");
}
}
WHEN("error is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->error("Topic", "This");
THEN("Output string is Topic ERR: This\\n") {
REQUIRE(log.str() == "Topic ERR: This\n");
}
}
WHEN("warn is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->warn("Topic", "This");
THEN("Output string is blank") {
REQUIRE(log.str() == "");
}
}
WHEN("info is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->info("Topic", "This");
THEN("Output string is blank") {
REQUIRE(log.str() == "");
}
}
WHEN("debug is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->debug("Topic", "This");
THEN("Output string is Topic DEBUG: This\\n") {
REQUIRE(log.str() == "Topic DEBUG: This\n");
}
}
}
GIVEN("log level of INFO and WARN") {
std::stringstream log;
std::unique_ptr<_Log> cut { _Log::make_log(log) };
cut->set_inclusive(false);
cut->clear_level(log_t::ALL);
cut->set_level(log_t::INFO);
cut->set_level(log_t::WARN);
cut->set_inclusive(false);
WHEN("fatal_error is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->fatal_error("Topic", "This");
THEN("Output string is blank") {
REQUIRE(log.str() == "");
}
}
WHEN("error is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->error("Topic", "This");
THEN("Output string is blank") {
REQUIRE(log.str() == "");
}
}
WHEN("warn is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->warn("Topic", "This");
THEN("Output string is Topic WARN: This\\n") {
REQUIRE(log.str() == "Topic WARN: This\n");
}
}
WHEN("info is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->info("Topic", "This");
THEN("Output string is Topic INFO: This\\n") {
REQUIRE(log.str() == "Topic INFO: This\n");
}
}
WHEN("debug is called with topic \"Topic\" and text \"This\"") {
log.clear();
cut->debug("Topic", "This");
THEN("Output string is blank") {
REQUIRE(log.str() == "");
}
}
}
}
SCENARIO( "_Log output filtering on topic name" ) {
std::stringstream log;
std::unique_ptr<_Log> cut { _Log::make_log(log) };
cut->set_inclusive(true);
cut->set_level(log_t::ALL);
WHEN("Topic is \"t1\"") {
cut->add_topic("t1");
cut->debug("t1") << "TEXT FOR T1 ";
cut->debug("t2") << "TEXT FOR T2 ";
cut->debug("t3") << "TEXT FOR T3";
THEN("Log text is \"TEXT FOR T1 \"") {
REQUIRE(log.str() == "t1 DEBUG: TEXT FOR T1 ");
}
}
WHEN("Topic is \"t2\"") {
cut->add_topic("t2");
cut->debug("t1") << "TEXT FOR T1 ";
cut->debug("t2") << "TEXT FOR T2 ";
cut->debug("t3") << "TEXT FOR T3";
THEN("Log text is \"TEXT FOR T2 \"") {
REQUIRE(log.str() == "t2 DEBUG: TEXT FOR T2 ");
}
}
WHEN("Topic is \"t3\"") {
cut->add_topic("t3");
cut->debug("t1") << "TEXT FOR T1 ";
cut->debug("t2") << "TEXT FOR T2 ";
cut->debug("t3") << "TEXT FOR T3";
THEN("Log text is \"TEXT FOR T3\"") {
REQUIRE(log.str() == "t3 DEBUG: TEXT FOR T3");
}
}
WHEN("Topic is \"t3\" and \"t2\"") {
cut->add_topic("t2");
cut->add_topic("t3");
cut->debug("t1") << "TEXT FOR T1 ";
cut->debug("t2") << "TEXT FOR T2 ";
cut->debug("t3") << "TEXT FOR T3";
THEN("Log text is \"TEXT FOR T2 TEXT FOR T3\"") {
REQUIRE(log.str() == "t2 DEBUG: TEXT FOR T2 t3 DEBUG: TEXT FOR T3");
}
}
}

View File

@ -0,0 +1,50 @@
#include <catch.hpp>
#include "Model.hpp"
#include "test_data.hpp" // get access to init_print, etc
using namespace Slic3r::Test;
SCENARIO("Model construction") {
GIVEN("A Slic3r Model") {
auto model {Slic3r::Model()};
auto sample_mesh {Slic3r::TriangleMesh::make_cube(20,20,20)};
sample_mesh.repair();
auto config {Slic3r::Config::new_from_defaults()};
std::shared_ptr<Slic3r::Print> print = std::make_shared<Slic3r::Print>();
print->apply_config(config);
WHEN("Model object is added") {
ModelObject* mo {model.add_object()};
THEN("Model object list == 1") {
REQUIRE(model.objects.size() == 1);
}
mo->add_volume(sample_mesh);
THEN("Model volume list == 1") {
REQUIRE(mo->volumes.size() == 1);
}
THEN("Model volume modifier is false") {
REQUIRE(mo->volumes.front()->modifier == false);
}
THEN("Mesh is equivalent to input mesh.") {
REQUIRE(sample_mesh.vertices() == mo->volumes.front()->mesh.vertices());
}
ModelInstance* inst {mo->add_instance()};
inst->rotation = 0;
inst->scaling_factor = 1.0;
model.arrange_objects(print->config.min_object_distance());
model.center_instances_around_point(Slic3r::Pointf(100,100));
print->auto_assign_extruders(mo);
print->add_model_object(mo);
THEN("Print works?") {
print->process();
auto gcode {std::stringstream("")};
print->export_gcode(gcode, true);
REQUIRE(gcode.str().size() > 0);
}
}
}
}

View File

@ -0,0 +1,98 @@
#include <catch.hpp>
#include <string>
#include "test_data.hpp"
#include "libslic3r.h"
using namespace Slic3r::Test;
using namespace std::literals;
SCENARIO("PrintObject: Perimeter generation") {
GIVEN("20mm cube and default config") {
auto config {Slic3r::Config::new_from_defaults()};
TestMesh m { TestMesh::cube_20x20x20 };
Slic3r::Model model;
auto event_counter {0U};
std::string stage;
int value {0};
auto callback {[&event_counter, &stage, &value] (int a, const char* b) { stage = std::string(b); event_counter++; value = a; }};
config->set("fill_density", 0);
WHEN("make_perimeters() is called") {
auto print {Slic3r::Test::init_print({m}, model, config)};
const auto& object = *(print->objects.at(0));
print->objects[0]->make_perimeters();
THEN("67 layers exist in the model") {
REQUIRE(object.layers.size() == 67);
}
THEN("Every layer in region 0 has 1 island of perimeters") {
for(auto* layer : object.layers) {
REQUIRE(layer->regions[0]->perimeters.size() == 1);
}
}
THEN("Every layer in region 0 has 3 paths in its perimeters list.") {
for(auto* layer : object.layers) {
REQUIRE(layer->regions[0]->perimeters.items_count() == 3);
}
}
}
}
}
SCENARIO("Print: Skirt generation") {
GIVEN("20mm cube and default config") {
auto config {Slic3r::Config::new_from_defaults()};
TestMesh m { TestMesh::cube_20x20x20 };
Slic3r::Model model;
auto event_counter {0U};
std::string stage;
int value {0};
config->set("skirt_height", 1);
config->set("skirt_distance", 1);
WHEN("Skirts is set to 2 loops") {
config->set("skirts", 2);
auto print {Slic3r::Test::init_print({m}, model, config)};
print->make_skirt();
THEN("Skirt Extrusion collection has 2 loops in it") {
REQUIRE(print->skirt.items_count() == 2);
REQUIRE(print->skirt.flatten().entities.size() == 2);
}
}
}
}
SCENARIO("Print: Brim generation") {
GIVEN("20mm cube and default config, 1mm first layer width") {
auto config {Slic3r::Config::new_from_defaults()};
TestMesh m { TestMesh::cube_20x20x20 };
Slic3r::Model model;
auto event_counter {0U};
std::string stage;
int value {0};
config->set("first_layer_extrusion_width", 1);
WHEN("Brim is set to 3mm") {
config->set("brim_width", 3);
auto print {Slic3r::Test::init_print({m}, model, config)};
print->make_brim();
THEN("Brim Extrusion collection has 3 loops in it") {
REQUIRE(print->brim.items_count() == 3);
}
}
WHEN("Brim is set to 6mm") {
config->set("brim_width", 6);
auto print {Slic3r::Test::init_print({m}, model, config)};
print->make_brim();
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") {
config->set("brim_width", 6);
config->set("first_layer_extrusion_width", 0.5);
auto print {Slic3r::Test::init_print({m}, model, config)};
print->make_brim();
THEN("Brim Extrusion collection has 12 loops in it") {
REQUIRE(print->brim.items_count() == 12);
}
}
}
}

View File

@ -0,0 +1,230 @@
#include <catch.hpp>
#include <regex>
#include "test_data.hpp"
#include "libslic3r.h"
#include "GCodeReader.hpp"
using namespace Slic3r::Test;
using namespace Slic3r;
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") {
GIVEN("A default configuration and a print test object") {
auto config {Slic3r::Config::new_from_defaults()};
auto gcode {std::stringstream("")};
WHEN("the output is executed with no support material") {
config->set("first_layer_extrusion_width", 0);
config->set("gcode_comments", true);
config->set("start_gcode", "");
Slic3r::Model model;
auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config)};
print->process();
Slic3r::Test::gcode(gcode, print);
auto exported {gcode.str()};
THEN("Some text output is generated.") {
REQUIRE(exported.size() > 0);
}
THEN("Exported text contains slic3r version") {
REQUIRE(exported.find(SLIC3R_VERSION) != std::string::npos);
}
THEN("Exported text contains git commit id") {
REQUIRE(exported.find("; Git Commit") != std::string::npos);
REQUIRE(exported.find(BUILD_COMMIT) != std::string::npos);
}
THEN("Exported text contains extrusion statistics.") {
REQUIRE(exported.find("; external perimeters extrusion width") != std::string::npos);
REQUIRE(exported.find("; perimeters extrusion width") != std::string::npos);
REQUIRE(exported.find("; infill extrusion width") != std::string::npos);
REQUIRE(exported.find("; solid infill extrusion width") != std::string::npos);
REQUIRE(exported.find("; top solid infill extrusion width") != std::string::npos);
REQUIRE(exported.find("; support material extrusion width") == std::string::npos);
REQUIRE(exported.find("; first layer extrusion width") == std::string::npos);
}
THEN("Exported text does not contain cooling markers (they were consumed)") {
REQUIRE(exported.find(";_EXTRUDE_SET_SPEED") == std::string::npos);
}
THEN("GCode preamble is emitted.") {
REQUIRE(exported.find("G21 ; set units to millimeters") != std::string::npos);
}
THEN("Config options emitted for print config, default region config, default object config") {
REQUIRE(exported.find("; first_layer_temperature") != std::string::npos);
REQUIRE(exported.find("; layer_height") != std::string::npos);
REQUIRE(exported.find("; fill_density") != std::string::npos);
}
THEN("Infill is emitted.") {
std::smatch has_match;
REQUIRE(std::regex_search(exported, has_match, infill_regex));
}
THEN("Perimeters are emitted.") {
std::smatch has_match;
REQUIRE(std::regex_search(exported, has_match, perimeters_regex));
}
THEN("Skirt is emitted.") {
std::smatch has_match;
REQUIRE(std::regex_search(exported, has_match, skirt_regex));
}
THEN("final Z height is ~20mm") {
double final_z {0.0};
auto reader {GCodeReader()};
reader.apply_config(print->config);
reader.parse(exported, [&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.15));
}
}
WHEN("output is executed with complete objects and two differently-sized meshes") {
Slic3r::Model model;
config->set("first_layer_extrusion_width", 0);
config->set("first_layer_height", 0.3);
config->set("support_material", false);
config->set("raft_layers", 0);
config->set("complete_objects", true);
config->set("gcode_comments", true);
config->set("between_objects_gcode", "; between-object-gcode");
auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20, TestMesh::ipadstand}, model, config)};
Slic3r::Test::gcode(gcode, print);
auto exported {gcode.str()};
THEN("Some text output is generated.") {
REQUIRE(exported.size() > 0);
}
THEN("Infill is emitted.") {
std::smatch has_match;
REQUIRE(std::regex_search(exported, has_match, infill_regex));
}
THEN("Perimeters are emitted.") {
std::smatch has_match;
REQUIRE(std::regex_search(exported, has_match, perimeters_regex));
}
THEN("Skirt is emitted.") {
std::smatch has_match;
REQUIRE(std::regex_search(exported, has_match, skirt_regex));
}
THEN("Between-object-gcode is emitted.") {
REQUIRE(exported.find("; between-object-gcode") != std::string::npos);
}
THEN("final Z height is ~27mm") {
double final_z {0.0};
auto reader {GCodeReader()};
reader.apply_config(print->config);
reader.parse(exported, [&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(30).margin(0.1)); // close enough
}
THEN("Z height resets on object change") {
double final_z {0.0};
bool reset {false};
auto reader {GCodeReader()};
reader.apply_config(print->config);
reader.parse(exported, [&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};
auto reader {GCodeReader()};
reader.apply_config(print->config);
reader.parse(exported, [&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") {
Slic3r::Model model;
config->set("first_layer_extrusion_width", 0);
config->set("support_material", true);
config->set("raft_layers", 3);
config->set("gcode_comments", true);
auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config)};
Slic3r::Test::gcode(gcode, print);
auto exported {gcode.str()};
THEN("Some text output is generated.") {
REQUIRE(exported.size() > 0);
}
THEN("Exported text contains extrusion statistics.") {
REQUIRE(exported.find("; external perimeters extrusion width") != std::string::npos);
REQUIRE(exported.find("; perimeters extrusion width") != std::string::npos);
REQUIRE(exported.find("; infill extrusion width") != std::string::npos);
REQUIRE(exported.find("; solid infill extrusion width") != std::string::npos);
REQUIRE(exported.find("; top solid infill extrusion width") != std::string::npos);
REQUIRE(exported.find("; support material extrusion width") != std::string::npos);
REQUIRE(exported.find("; first layer extrusion width") == std::string::npos);
}
THEN("Raft is emitted.") {
REQUIRE(exported.find("; raft") != std::string::npos);
}
}
WHEN("the output is executed with a separate first layer extrusion width") {
Slic3r::Model model;
config->set("first_layer_extrusion_width", 0.5);
auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config)};
Slic3r::Test::gcode(gcode, print);
auto exported {gcode.str()};
THEN("Some text output is generated.") {
REQUIRE(exported.size() > 0);
}
THEN("Exported text contains extrusion statistics.") {
REQUIRE(exported.find("; external perimeters extrusion width") != std::string::npos);
REQUIRE(exported.find("; perimeters extrusion width") != std::string::npos);
REQUIRE(exported.find("; infill extrusion width") != std::string::npos);
REQUIRE(exported.find("; solid infill extrusion width") != std::string::npos);
REQUIRE(exported.find("; top solid infill extrusion width") != std::string::npos);
REQUIRE(exported.find("; support material extrusion width") == std::string::npos);
REQUIRE(exported.find("; first layer extrusion width") != std::string::npos);
}
}
WHEN("Cooling is enabled and the fan is disabled.") {
config->set("cooling", true);
config->set("disable_fan_first_layers", 5);
Slic3r::Model model;
auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config)};
Slic3r::Test::gcode(gcode, print);
auto exported {gcode.str()};
THEN("GCode to disable fan is emitted."){
REQUIRE(exported.find("M107") != std::string::npos);
}
}
WHEN("end_gcode exists with layer_num and layer_z") {
config->set("end_gcode", "; Layer_num [layer_num]\n; Layer_z [layer_z]");
config->set("layer_height", 0.1);
config->set("first_layer_height", 0.1);
Slic3r::Model model;
auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config)};
Slic3r::Test::gcode(gcode, print);
auto exported {gcode.str()};
THEN("layer_num and layer_z are processed in the end gcode") {\
REQUIRE(exported.find("; Layer_num 199") != std::string::npos);
REQUIRE(exported.find("; Layer_z 20") != std::string::npos);
}
}
gcode.clear();
}
}

View File

@ -0,0 +1,122 @@
#include <catch.hpp>
#include "test_data.hpp" // get access to init_print, etc
#include "GCodeReader.hpp"
using namespace Slic3r::Test;
using namespace Slic3r;
SCENARIO("Original Slic3r Skirt/Brim tests", "[!mayfail]") {
GIVEN("Configuration with a skirt height of 2") {
auto config {Config::new_from_defaults()};
config->set("skirts", 1);
config->set("skirt_height", 2);
config->set("perimeters", 0);
config->set("support_material_speed", 99);
// avoid altering speeds unexpectedly
config->set("cooling", 0);
config->set("first_layer_speed", "100%");
WHEN("multiple objects are printed") {
auto gcode {std::stringstream("")};
Slic3r::Model model;
auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20, TestMesh::cube_20x20x20}, model, config)};
std::map<double, bool> layers_with_skirt;
Slic3r::Test::gcode(gcode, print);
auto parser {Slic3r::GCodeReader()};
parser.parse_stream(gcode, [&layers_with_skirt, &config] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line)
{
if (self.Z > 0) {
if (line.extruding() && line.new_F() == config->getFloat("support_material_speed") * 60.0) {
layers_with_skirt[self.Z] = 1;
}
}
});
THEN("skirt_height is honored") {
REQUIRE(layers_with_skirt.size() == (size_t)config->getInt("skirt_height"));
}
}
}
GIVEN("A default configuration") {
auto config {Config::new_from_defaults()};
config->set("support_material_speed", 99);
// avoid altering speeds unexpectedly
config->set("cooling", 0);
config->set("first_layer_speed", "100%");
// remove noise from top/solid layers
config->set("top_solid_layers", 0);
config->set("bottom_solid_layers", 0);
WHEN("Brim width is set to 5") {
config->set("perimeters", 0);
config->set("skirts", 0);
config->set("brim_width", 5);
THEN("Brim is generated") {
REQUIRE(false);
}
}
WHEN("Skirt area is smaller than the brim") {
config->set("skirts", 1);
config->set("brim_width", 10);
THEN("GCode generates successfully.") {
REQUIRE(false);
}
}
WHEN("Skirt height is 0 and skirts > 0") {
config->set("skirts", 2);
config->set("skirt_height", 0);
THEN("GCode generates successfully.") {
REQUIRE(false);
}
}
WHEN("Perimeter extruder = 2 and support extruders = 3") {
THEN("Brim is printed with the extruder used for the perimeters of first object") {
REQUIRE(false);
}
}
WHEN("Perimeter extruder = 2, support extruders = 3, raft is enabled") {
THEN("brim is printed with same extruder as skirt") {
REQUIRE(false);
}
}
WHEN("Object is plated with overhang support and a brim") {
config->set("layer_height", 0.4);
config->set("first_layer_height", 0.4);
config->set("skirts", 1);
config->set("skirt_distance", 0);
config->set("support_material_speed", 99);
config->set("perimeter_extruder", 1);
config->set("support_material_extruder", 2);
config->set("cooling", 0); // to prevent speeds to be altered
config->set("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();
config->set("support_material", true); // to prevent speeds to be altered
THEN("skirt length is large enough to contain object with support") {
REQUIRE(false);
}
}
WHEN("Large minimum skirt length is used.") {
config->set("min_skirt_length", 20);
auto gcode {std::stringstream("")};
Slic3r::Model model;
auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config)};
THEN("Gcode generation doesn't crash") {
Slic3r::Test::gcode(gcode, print);
auto exported {gcode.str()};
REQUIRE(exported.size() > 0);
}
}
}
}

View File

@ -0,0 +1,295 @@
#include <catch.hpp>
#include <libslic3r/IO.hpp>
#include <libslic3r/GCodeReader.hpp>
#include "libslic3r.h"
#include "TriangleMesh.hpp"
#include "Model.hpp"
#include "SupportMaterial.hpp"
using namespace std;
using namespace Slic3r;
void test_1_checks(Print &print, bool &a, bool &b, bool &c, bool &d);
bool test_6_checks(Print &print);
// Testing 0.1: supports material member functions.
TEST_CASE("", "")
{
// Create a mesh & modelObject.
TriangleMesh mesh = TriangleMesh::make_cube(20, 20, 20);
// Create modelObject.
Model model = Model();
ModelObject *object = model.add_object();
object->add_volume(mesh);
model.add_default_instances();
// Align to origin.
model.align_instances_to_origin();
// Create Print.
Print print = Print();
vector<coordf_t> contact_z = {1.9};
vector<coordf_t> top_z = {1.1};
print.default_object_config.support_material = 1;
print.default_object_config.set_deserialize("raft_layers", "3");
print.add_model_object(model.objects[0]);
print.objects.front()->_slice();
SupportMaterial *support = print.objects.front()->_support_material();
support->generate(print.objects.front());
REQUIRE(print.objects.front()->support_layer_count() == 3);
}
// Test 1.
SCENARIO("SupportMaterial: support_layers_z and contact_distance")
{
GIVEN("A print object having one modelObject") {
// Create a mesh & modelObject.
TriangleMesh mesh = TriangleMesh::make_cube(20, 20, 20);
// Create modelObject.
Model model = Model();
ModelObject *object = model.add_object();
object->add_volume(mesh);
model.add_default_instances();
// Align to origin.
model.align_instances_to_origin();
// Create Print.
Print print = Print();
print.default_object_config.set_deserialize("support_material", "1");
WHEN("First layer height = 0.4") {
print.default_object_config.set_deserialize("layer_height", "0.2");
print.default_object_config.set_deserialize("first_layer_height", "0.4");
print.add_model_object(model.objects[0]);
print.objects.front()->_slice();
bool a, b, c, d;
test_1_checks(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") {
print.default_object_config.set_deserialize("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();
bool a, b, c, d;
test_1_checks(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]") {
print.default_object_config.set_deserialize("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();
bool a, b, c, d;
test_1_checks(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);
}
}
}
}
// Test 8.
TEST_CASE("SupportMaterial: forced support is generated", "")
{
// 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();
vector<coordf_t> contact_z = {1.9};
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);
}
// Test 6.
SCENARIO("SupportMaterial: Checking bridge speed")
{
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.
}
}
}
void test_1_checks(Print &print, bool &a, bool &b, bool &c, bool &d)
{
vector<coordf_t> contact_z = {1.9};
vector<coordf_t> top_z = {1.1};
SupportMaterial *support = print.objects.front()->_support_material();
vector<coordf_t>
support_z = support->support_layers_z(contact_z, top_z, print.default_object_config.layer_height);
a = (support_z[0] == print.default_object_config.first_layer_height.value);
b = true;
for (size_t i = 1; i < support_z.size(); ++i)
if (support_z[i] - support_z[i - 1] <= 0) b = false;
c = true;
for (size_t i = 1; i < support_z.size(); ++i)
if (support_z[i] - support_z[i - 1] > print.config.nozzle_diameter.get_at(0) + EPSILON)
c = false;
coordf_t expected_top_spacing = support
->contact_distance(print.default_object_config.layer_height,
print.config.nozzle_diameter.get_at(0));
bool wrong_top_spacing = 0;
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;
}
// 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;
}

View File

@ -0,0 +1,29 @@
#include <catch.hpp>
#include <sstream>
#include "test_data.hpp"
using namespace Slic3r::Test;
SCENARIO("init_print functionality") {
GIVEN("A default config") {
config_ptr config {Slic3r::Config::new_from_defaults()};
std::stringstream gcode;
WHEN("init_print is called with a single mesh.") {
Slic3r::Model model;
auto print = init_print({TestMesh::cube_20x20x20}, model, config, true);
gcode.clear();
THEN("One mesh/printobject is in the resulting Print object.") {
REQUIRE(print->objects.size() == 1);
}
THEN("print->process() doesn't crash.") {
REQUIRE_NOTHROW(print->process());
}
THEN("Export gcode functions outputs text.") {
print->process();
print->export_gcode(gcode, true);
REQUIRE(gcode.str().size() > 0);
}
}
}
}

View File

@ -0,0 +1,428 @@
#include <catch.hpp>
#include "TriangleMesh.hpp"
#include "libslic3r.h"
#include "Point.hpp"
#include "test_options.hpp"
#include "Config.hpp"
#include "Model.hpp"
#include "test_data.hpp"
#include "Log.hpp"
#include <algorithm>
#include <future>
#include <chrono>
using namespace Slic3r;
using namespace std;
SCENARIO( "TriangleMesh: Basic mesh statistics") {
GIVEN( "A 20mm cube, built from constexpr std::array" ) {
constexpr std::array<Pointf3, 8> vertices { Pointf3(20,20,0), Pointf3(20,0,0), Pointf3(0,0,0), Pointf3(0,20,0), Pointf3(20,20,20), Pointf3(0,20,20), Pointf3(0,0,20), Pointf3(20,0,20) };
constexpr std::array<Point3, 12> facets { Point3(0,1,2), Point3(0,2,3), Point3(4,5,6), Point3(4,6,7), Point3(0,4,7), Point3(0,7,1), Point3(1,7,6), Point3(1,6,2), Point3(2,6,5), Point3(2,5,3), Point3(4,0,3), Point3(4,3,5) };
auto cube {TriangleMesh(vertices, facets)};
cube.repair();
THEN( "Volume is appropriate for 20mm square cube.") {
REQUIRE(abs(cube.volume() - 20.0*20.0*20.0) < 1e-2);
}
THEN( "Vertices array matches input.") {
for (auto i = 0U; i < cube.vertices().size(); i++) {
REQUIRE(cube.vertices().at(i) == vertices.at(i));
}
for (auto i = 0U; i < vertices.size(); i++) {
REQUIRE(vertices.at(i) == cube.vertices().at(i));
}
}
THEN( "Vertex count matches vertex array size.") {
REQUIRE(cube.facets_count() == facets.size());
}
THEN( "Facet array matches input.") {
for (auto i = 0U; i < cube.facets().size(); i++) {
REQUIRE(cube.facets().at(i) == facets.at(i));
}
for (auto i = 0U; i < facets.size(); i++) {
REQUIRE(facets.at(i) == cube.facets().at(i));
}
}
THEN( "Facet count matches facet array size.") {
REQUIRE(cube.facets_count() == facets.size());
}
THEN( "Number of normals is equal to the number of facets.") {
REQUIRE(cube.normals().size() == facets.size());
}
THEN( "center() returns the center of the object.") {
REQUIRE(cube.center() == Pointf3(10.0,10.0,10.0));
}
THEN( "Size of cube is (20,20,20)") {
REQUIRE(cube.size() == Pointf3(20,20,20));
}
}
GIVEN( "A 20mm cube with one corner on the origin") {
const Pointf3s vertices { Pointf3(20,20,0), Pointf3(20,0,0), Pointf3(0,0,0), Pointf3(0,20,0), Pointf3(20,20,20), Pointf3(0,20,20), Pointf3(0,0,20), Pointf3(20,0,20) };
const Point3s facets { Point3(0,1,2), Point3(0,2,3), Point3(4,5,6), Point3(4,6,7), Point3(0,4,7), Point3(0,7,1), Point3(1,7,6), Point3(1,6,2), Point3(2,6,5), Point3(2,5,3), Point3(4,0,3), Point3(4,3,5) };
auto cube {TriangleMesh(vertices, facets)};
cube.repair();
THEN( "Volume is appropriate for 20mm square cube.") {
REQUIRE(abs(cube.volume() - 20.0*20.0*20.0) < 1e-2);
}
THEN( "Vertices array matches input.") {
for (auto i = 0U; i < cube.vertices().size(); i++) {
REQUIRE(cube.vertices().at(i) == vertices.at(i));
}
for (auto i = 0U; i < vertices.size(); i++) {
REQUIRE(vertices.at(i) == cube.vertices().at(i));
}
}
THEN( "Vertex count matches vertex array size.") {
REQUIRE(cube.facets_count() == facets.size());
}
THEN( "Facet array matches input.") {
for (auto i = 0U; i < cube.facets().size(); i++) {
REQUIRE(cube.facets().at(i) == facets.at(i));
}
for (auto i = 0U; i < facets.size(); i++) {
REQUIRE(facets.at(i) == cube.facets().at(i));
}
}
THEN( "Facet count matches facet array size.") {
REQUIRE(cube.facets_count() == facets.size());
}
THEN( "Number of normals is equal to the number of facets.") {
REQUIRE(cube.normals().size() == facets.size());
}
THEN( "center() returns the center of the object.") {
REQUIRE(cube.center() == Pointf3(10.0,10.0,10.0));
}
THEN( "Size of cube is (20,20,20)") {
REQUIRE(cube.size() == Pointf3(20,20,20));
}
}
}
SCENARIO( "TriangleMesh: Transformation functions affect mesh as expected.") {
GIVEN( "A 20mm cube with one corner on the origin") {
const Pointf3s vertices { Pointf3(20,20,0), Pointf3(20,0,0), Pointf3(0,0,0), Pointf3(0,20,0), Pointf3(20,20,20), Pointf3(0,20,20), Pointf3(0,0,20), Pointf3(20,0,20) };
const std::vector<Point3> facets { Point3(0,1,2), Point3(0,2,3), Point3(4,5,6), Point3(4,6,7), Point3(0,4,7), Point3(0,7,1), Point3(1,7,6), Point3(1,6,2), Point3(2,6,5), Point3(2,5,3), Point3(4,0,3), Point3(4,3,5) };
auto cube {TriangleMesh(vertices, facets)};
cube.repair();
WHEN( "The cube is scaled 200\% uniformly") {
cube.scale(2.0);
THEN( "The volume is equivalent to 40x40x40 (all dimensions increased by 200\%") {
REQUIRE(abs(cube.volume() - 40.0*40.0*40.0) < 1e-2);
}
}
WHEN( "The resulting cube is scaled 200\% in the X direction") {
cube.scale(Vectorf3(2.0, 1, 1));
THEN( "The volume is doubled.") {
REQUIRE(abs(cube.volume() - 2*20.0*20.0*20.0) < 1e-2);
}
THEN( "The X coordinate size is 200\%.") {
REQUIRE(cube.vertices().at(0).x == 40.0);
}
}
WHEN( "The cube is scaled 25\% in the X direction") {
cube.scale(Vectorf3(0.25, 1, 1));
THEN( "The volume is 25\% of the previous volume.") {
REQUIRE(abs(cube.volume() - 0.25*20.0*20.0*20.0) < 1e-2);
}
THEN( "The X coordinate size is 25\% from previous.") {
REQUIRE(cube.vertices().at(0).x == 5.0);
}
}
WHEN( "The cube is rotated 45 degrees.") {
cube.rotate(45.0, Slic3r::Point(20,20));
THEN( "The X component of the size is sqrt(2)*20") {
REQUIRE(abs(cube.size().x - sqrt(2.0)*20) < 1e-2);
}
}
WHEN( "The cube is translated (5, 10, 0) units with a Pointf3 ") {
cube.translate(Pointf3(5.0, 10.0, 0.0));
THEN( "The first vertex is located at 25, 30, 0") {
REQUIRE(cube.vertices().at(0) == Pointf3(25.0, 30.0, 0.0));
}
}
WHEN( "The cube is translated (5, 10, 0) units with 3 doubles") {
cube.translate(5.0, 10.0, 0.0);
THEN( "The first vertex is located at 25, 30, 0") {
REQUIRE(cube.vertices().at(0) == Pointf3(25.0, 30.0, 0.0));
}
}
WHEN( "The cube is translated (5, 10, 0) units and then aligned to origin") {
cube.translate(5.0, 10.0, 0.0);
cube.align_to_origin();
THEN( "The third vertex is located at 0,0,0") {
REQUIRE(cube.vertices().at(2) == Pointf3(0.0, 0.0, 0.0));
}
}
}
}
SCENARIO( "TriangleMesh: slice behavior.") {
GIVEN( "A 20mm cube with one corner on the origin") {
const Pointf3s vertices { Pointf3(20,20,0), Pointf3(20,0,0), Pointf3(0,0,0), Pointf3(0,20,0), Pointf3(20,20,20), Pointf3(0,20,20), Pointf3(0,0,20), Pointf3(20,0,20) };
const std::vector<Point3> facets { Point3(0,1,2), Point3(0,2,3), Point3(4,5,6), Point3(4,6,7), Point3(0,4,7), Point3(0,7,1), Point3(1,7,6), Point3(1,6,2), Point3(2,6,5), Point3(2,5,3), Point3(4,0,3), Point3(4,3,5) };
auto cube {TriangleMesh(vertices, facets)};
cube.repair();
WHEN("Cube is sliced with z = [0,2,4,8,6,8,10,12,14,16,18,20]") {
std::vector<double> z { 0,2,4,8,6,8,10,12,14,16,18,20 };
auto result {cube.slice(z)};
THEN( "The correct number of polygons are returned per layer.") {
for (auto i = 0U; i < z.size(); i++) {
REQUIRE(result.at(i).size() == 1);
}
}
THEN( "The area of the returned polygons is correct.") {
for (auto i = 0U; i < z.size(); i++) {
REQUIRE(result.at(i).at(0).area() == 20.0*20/(std::pow(SCALING_FACTOR,2)));
}
}
}
}
GIVEN( "A STL with an irregular shape.") {
const Pointf3s vertices {Pointf3(0,0,0),Pointf3(0,0,20),Pointf3(0,5,0),Pointf3(0,5,20),Pointf3(50,0,0),Pointf3(50,0,20),Pointf3(15,5,0),Pointf3(35,5,0),Pointf3(15,20,0),Pointf3(50,5,0),Pointf3(35,20,0),Pointf3(15,5,10),Pointf3(50,5,20),Pointf3(35,5,10),Pointf3(35,20,10),Pointf3(15,20,10)};
const Point3s facets {Point3(0,1,2),Point3(2,1,3),Point3(1,0,4),Point3(5,1,4),Point3(0,2,4),Point3(4,2,6),Point3(7,6,8),Point3(4,6,7),Point3(9,4,7),Point3(7,8,10),Point3(2,3,6),Point3(11,3,12),Point3(7,12,9),Point3(13,12,7),Point3(6,3,11),Point3(11,12,13),Point3(3,1,5),Point3(12,3,5),Point3(5,4,9),Point3(12,5,9),Point3(13,7,10),Point3(14,13,10),Point3(8,15,10),Point3(10,15,14),Point3(6,11,8),Point3(8,11,15),Point3(15,11,13),Point3(14,15,13)};
auto cube {TriangleMesh(vertices, facets)};
cube.repair();
WHEN(" a top tangent plane is sliced") {
auto slices {cube.slice({5.0, 10.0})};
THEN( "its area is included") {
REQUIRE(slices.at(0).at(0).area() > 0);
REQUIRE(slices.at(1).at(0).area() > 0);
}
}
WHEN(" a model that has been transformed is sliced") {
cube.mirror_z();
auto slices {cube.slice({-5.0, -10.0})};
THEN( "it is sliced properly (mirrored bottom plane area is included)") {
REQUIRE(slices.at(0).at(0).area() > 0);
REQUIRE(slices.at(1).at(0).area() > 0);
}
}
}
}
SCENARIO( "make_xxx functions produce meshes.") {
GIVEN("make_cube() function") {
WHEN("make_cube() is called with arguments 20,20,20") {
auto cube {TriangleMesh::make_cube(20,20,20)};
THEN("The resulting mesh has one and only one vertex at 0,0,0") {
auto verts {cube.vertices()};
REQUIRE(std::count_if(verts.begin(), verts.end(), [](Pointf3& t) { return t.x == 0 && t.y == 0 && t.z == 0; } ) == 1);
}
THEN("The mesh volume is 20*20*20") {
REQUIRE(abs(cube.volume() - 20.0*20.0*20.0) < 1e-2);
}
THEN("The resulting mesh is in the repaired state.") {
REQUIRE(cube.repaired == true);
}
THEN("There are 12 facets.") {
REQUIRE(cube.facets().size() == 12);
}
}
}
GIVEN("make_cylinder() function") {
WHEN("make_cylinder() is called with arguments 10,10, PI / 3") {
auto cyl {TriangleMesh::make_cylinder(10, 10, PI / 243.0)};
double angle = (2*PI / floor(2*PI / (PI / 243.0)));
THEN("The resulting mesh has one and only one vertex at 0,0,0") {
auto verts {cyl.vertices()};
REQUIRE(std::count_if(verts.begin(), verts.end(), [](Pointf3& t) { return t.x == 0 && t.y == 0 && t.z == 0; } ) == 1);
}
THEN("The resulting mesh has one and only one vertex at 0,0,10") {
auto verts {cyl.vertices()};
REQUIRE(std::count_if(verts.begin(), verts.end(), [](Pointf3& t) { return t.x == 0 && t.y == 0 && t.z == 10; } ) == 1);
}
THEN("Resulting mesh has 2 + (2*PI/angle * 2) vertices.") {
REQUIRE(cyl.vertices().size() == (2 + ((2*PI/angle)*2)));
}
THEN("Resulting mesh has 2*PI/angle * 4 facets") {
REQUIRE(cyl.facets().size() == (2*PI/angle)*4);
}
THEN("The resulting mesh is in the repaired state.") {
REQUIRE(cyl.repaired == true);
}
THEN( "The mesh volume is approximately 10pi * 10^2") {
REQUIRE(abs(cyl.volume() - (10.0 * M_PI * std::pow(10,2))) < 1);
}
}
}
GIVEN("make_sphere() function") {
WHEN("make_sphere() is called with arguments 10, PI / 3") {
auto sph {TriangleMesh::make_sphere(10, PI / 243.0)};
double angle = (2.0*PI / floor(2.0*PI / (PI / 243.0)));
THEN("Resulting mesh has one point at 0,0,-10 and one at 0,0,10") {
auto verts {sph.vertices()};
REQUIRE(std::count_if(verts.begin(), verts.end(), [](Pointf3& t) { return t.x == 0 && t.y == 0 && t.z == 10; } ) == 1);
REQUIRE(std::count_if(verts.begin(), verts.end(), [](Pointf3& t) { return t.x == 0 && t.y == 0 && t.z == -10; } ) == 1);
}
THEN("The resulting mesh is in the repaired state.") {
REQUIRE(sph.repaired == true);
}
THEN( "The mesh volume is approximately 4/3 * pi * 10^3") {
REQUIRE(abs(sph.volume() - (4.0/3.0 * M_PI * std::pow(10,3))) < 1); // 1% tolerance?
}
}
}
}
SCENARIO( "TriangleMesh: split functionality.") {
GIVEN( "A 20mm cube with one corner on the origin") {
const Pointf3s vertices { Pointf3(20,20,0), Pointf3(20,0,0), Pointf3(0,0,0), Pointf3(0,20,0), Pointf3(20,20,20), Pointf3(0,20,20), Pointf3(0,0,20), Pointf3(20,0,20) };
const Point3s facets { Point3(0,1,2), Point3(0,2,3), Point3(4,5,6), Point3(4,6,7), Point3(0,4,7), Point3(0,7,1), Point3(1,7,6), Point3(1,6,2), Point3(2,6,5), Point3(2,5,3), Point3(4,0,3), Point3(4,3,5) };
auto cube {TriangleMesh(vertices, facets)};
cube.repair();
WHEN( "The mesh is split into its component parts.") {
auto meshes {cube.split()};
THEN(" The bounding box statistics are propagated to the split copies") {
REQUIRE(meshes.size() == 1);
REQUIRE(meshes.at(0)->bb3() == cube.bb3());
}
}
}
GIVEN( "Two 20mm cubes, each with one corner on the origin, merged into a single TriangleMesh") {
const Pointf3s vertices { Pointf3(20,20,0), Pointf3(20,0,0), Pointf3(0,0,0), Pointf3(0,20,0), Pointf3(20,20,20), Pointf3(0,20,20), Pointf3(0,0,20), Pointf3(20,0,20) };
const Point3s facets { Point3(0,1,2), Point3(0,2,3), Point3(4,5,6), Point3(4,6,7), Point3(0,4,7), Point3(0,7,1), Point3(1,7,6), Point3(1,6,2), Point3(2,6,5), Point3(2,5,3), Point3(4,0,3), Point3(4,3,5) };
auto cube {TriangleMesh(vertices, facets)};
cube.repair();
auto cube2 {TriangleMesh(vertices, facets)};
cube2.repair();
cube.merge(cube2);
cube.repair();
WHEN( "The combined mesh is split") {
auto meshes {cube.split()};
THEN( "Two meshes are in the output vector.") {
REQUIRE(meshes.size() == 2);
}
}
}
}
SCENARIO( "TriangleMesh: Mesh merge functions") {
GIVEN( "Two 20mm cubes, each with one corner on the origin") {
const Pointf3s vertices { Pointf3(20,20,0), Pointf3(20,0,0), Pointf3(0,0,0), Pointf3(0,20,0), Pointf3(20,20,20), Pointf3(0,20,20), Pointf3(0,0,20), Pointf3(20,0,20) };
const Point3s facets { Point3(0,1,2), Point3(0,2,3), Point3(4,5,6), Point3(4,6,7), Point3(0,4,7), Point3(0,7,1), Point3(1,7,6), Point3(1,6,2), Point3(2,6,5), Point3(2,5,3), Point3(4,0,3), Point3(4,3,5) };
auto cube {TriangleMesh(vertices, facets)};
cube.repair();
auto cube2 {TriangleMesh(vertices, facets)};
cube2.repair();
WHEN( "The two meshes are merged") {
cube.merge(cube2);
cube.repair();
THEN( "There are twice as many facets in the merged mesh as the original.") {
REQUIRE(cube.stats().number_of_facets == 2 * cube2.stats().number_of_facets);
}
}
}
}
SCENARIO( "TriangleMeshSlicer: Cut behavior.") {
GIVEN( "A 20mm cube with one corner on the origin") {
const Pointf3s vertices { Pointf3(20,20,0), Pointf3(20,0,0), Pointf3(0,0,0), Pointf3(0,20,0), Pointf3(20,20,20), Pointf3(0,20,20), Pointf3(0,0,20), Pointf3(20,0,20) };
const Point3s facets { Point3(0,1,2), Point3(0,2,3), Point3(4,5,6), Point3(4,6,7), Point3(0,4,7), Point3(0,7,1), Point3(1,7,6), Point3(1,6,2), Point3(2,6,5), Point3(2,5,3), Point3(4,0,3), Point3(4,3,5) };
auto cube {TriangleMesh(vertices, facets)};
cube.repair();
WHEN( "Object is cut at the bottom") {
TriangleMesh upper {};
TriangleMesh lower {};
cube.cut(Z, 0, &upper, &lower);
THEN("Upper mesh has all facets except those belonging to the slicing plane.") {
REQUIRE(upper.facets_count() == 12);
}
THEN("Lower mesh has no facets.") {
REQUIRE(lower.facets_count() == 0);
}
}
WHEN( "Object is cut at the center") {
TriangleMesh upper {};
TriangleMesh lower {};
cube.cut(Z, 10, &upper, &lower);
THEN("Upper mesh has 2 external horizontal facets, 3 facets on each side, and 6 facets on the triangulated side (2 + 12 + 6).") {
REQUIRE(upper.facets_count() == 2+12+6);
}
THEN("Lower mesh has 2 external horizontal facets, 3 facets on each side, and 6 facets on the triangulated side (2 + 12 + 6).") {
REQUIRE(lower.facets_count() == 2+12+6);
}
}
}
}
#ifdef TEST_PERFORMANCE
TEST_CASE("Regression test for issue #4486 - files take forever to slice") {
TriangleMesh mesh;
auto config {Slic3r::Config::new_from_defaults()};
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);
Slic3r::Model model;
auto print {Slic3r::Test::init_print({mesh}, model, config)};
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::chrono::milliseconds span {120000};
bool timedout {false};
if(fut.wait_for(span) == std::future_status::timeout) {
timedout = true;
}
REQUIRE(timedout == false);
}
#endif // TEST_PERFORMANCE
#ifdef BUILD_PROFILE
TEST_CASE("Profile test for issue #4486 - files take forever to slice") {
TriangleMesh mesh;
auto config {Slic3r::Config::new_from_defaults()};
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%");
Slic3r::Model model;
auto print {Slic3r::Test::init_print({mesh}, model, config)};
print->status_cb = [] (int ln, const std::string& msg) { Slic3r::Log::info("Print") << ln << " " << msg << "\n";};
print->process();
REQUIRE(true);
}
#endif //BUILD_PROFILE

322
src/test/test_data.cpp Normal file

File diff suppressed because one or more lines are too long

79
src/test/test_data.hpp Normal file
View File

@ -0,0 +1,79 @@
#ifndef SLIC3R_TEST_DATA_HPP
#define SLIC3R_TEST_DATA_HPP
#include "../libslic3r/Point.hpp"
#include "../libslic3r/TriangleMesh.hpp"
#include "../libslic3r/Geometry.hpp"
#include "../libslic3r/Model.hpp"
#include "../libslic3r/Print.hpp"
#include "../libslic3r/Config.hpp"
#include "test_options.hpp"
#include <unordered_map>
namespace Slic3r { namespace Test {
/// Enumeration of test meshes
enum class TestMesh {
A,
L,
V,
_40x10,
cube_20x20x20,
sphere_50mm,
bridge,
bridge_with_hole,
cube_with_concave_hole,
cube_with_hole,
gt2_teeth,
ipadstand,
overhang,
pyramid,
sloping_hole,
slopy_cube,
small_dorito,
step,
two_hollow_squares
};
// Neccessary for <c++17
struct TestMeshHash {
std::size_t operator()(TestMesh tm) const {
return static_cast<std::size_t>(tm);
}
};
/// Mesh enumeration to name mapping
extern const std::unordered_map<TestMesh, const std::string, TestMeshHash> mesh_names;
/// Port of Slic3r::Test::mesh
/// Basic cubes/boxes should call TriangleMesh::make_cube() directly and rescale/translate it
TriangleMesh mesh(TestMesh m);
TriangleMesh mesh(TestMesh m, Vec3f translate, Vec3f scale = Vec3f(1.0, 1.0, 1.0));
TriangleMesh mesh(TestMesh m, Vec3f translate, double scale = 1.0);
/// Templated function to see if two values are equivalent (+/- epsilon)
template <typename T>
bool _equiv(const T& a, const T& b) { return abs(a - b) < Slic3r::Geometry::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, Vec3f translate = Vec3f(0,0,0), Vec3f scale = Vec3f(1.0,1.0,1.0));
//Slic3r::Model model(const std::string& model_name, TestMesh m, Vec3f translate = Vec3f(0,0,0), double scale = 1.0);
Slic3r::Model model(const std::string& model_name, TriangleMesh&& _mesh);
void init_print(Print& print, std::initializer_list<TestMesh> meshes, Slic3r::Model& model, DynamicPrintConfig* _config = Slic3r::DynamicPrintConfig::new_from_defaults(), bool comments = false);
void init_print(Print& print, std::initializer_list<TriangleMesh> meshes, Slic3r::Model& model, DynamicPrintConfig* _config = Slic3r::DynamicPrintConfig::new_from_defaults(), bool comments = false);
void gcode(std::string& gcode, Print& print);
std::string read_to_string(const std::string& name);
void clean_file(const std::string& name, const std::string& ext, bool glob = false);
} } // namespace Slic3r::Test
#endif // SLIC3R_TEST_DATA_HPP

View File

@ -0,0 +1,2 @@
#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file
#include <catch.hpp>

14
src/test/test_options.hpp Normal file
View File

@ -0,0 +1,14 @@
#ifndef TEST_OPTIONS_HPP
#include <string>
/// Directory path, passed in from the outside, for the path to the test inputs dir.
constexpr auto* testfile_dir {"C:/local/Slic3rcpp/src/test/inputs/"};
inline std::string testfile(std::string filename) {
std::string result;
result.append(testfile_dir);
result.append(filename);
return result;
}
#endif // TEST_OPTIONS_HPP

View File

@ -0,0 +1,14 @@
#ifndef TEST_OPTIONS_HPP
#include <string>
/// Directory path, passed in from the outside, for the path to the test inputs dir.
constexpr auto* testfile_dir {"@TESTFILE_DIR@"};
inline std::string testfile(std::string filename) {
std::string result;
result.append(testfile_dir);
result.append(filename);
return result;
}
#endif // TEST_OPTIONS_HPP