diff --git a/resources/icons/PrusaSlicer-gcodeviewer_128px.png b/resources/icons/PrusaSlicer-gcodeviewer_128px.png
new file mode 100644
index 000000000..d8e3e438b
Binary files /dev/null and b/resources/icons/PrusaSlicer-gcodeviewer_128px.png differ
diff --git a/resources/icons/PrusaSlicer-gcodeviewer_192px.png b/resources/icons/PrusaSlicer-gcodeviewer_192px.png
new file mode 100644
index 000000000..0e0243012
Binary files /dev/null and b/resources/icons/PrusaSlicer-gcodeviewer_192px.png differ
diff --git a/resources/icons/PrusaSlicerGCodeViewer_128px.png b/resources/icons/PrusaSlicerGCodeViewer_128px.png
deleted file mode 100644
index 0bf85abbd..000000000
Binary files a/resources/icons/PrusaSlicerGCodeViewer_128px.png and /dev/null differ
diff --git a/resources/icons/add.svg b/resources/icons/add.svg
index 8a9b253de..37050d748 100644
--- a/resources/icons/add.svg
+++ b/resources/icons/add.svg
@@ -1,17 +1,22 @@
-
+
diff --git a/resources/icons/arrange.svg b/resources/icons/arrange.svg
index 4f30e979e..62cf939e9 100644
--- a/resources/icons/arrange.svg
+++ b/resources/icons/arrange.svg
@@ -1,24 +1,23 @@
-
+
diff --git a/resources/icons/copy.svg b/resources/icons/copy.svg
index 9b8430dd7..345c2590b 100644
--- a/resources/icons/copy.svg
+++ b/resources/icons/copy.svg
@@ -1,37 +1,29 @@
-
+
diff --git a/resources/icons/delete_all.svg b/resources/icons/delete_all.svg
index 80e2e503c..dfa943812 100644
--- a/resources/icons/delete_all.svg
+++ b/resources/icons/delete_all.svg
@@ -1,31 +1,17 @@
-
+
diff --git a/resources/icons/instance_add.svg b/resources/icons/instance_add.svg
index 5ef492cfa..a466c51db 100644
--- a/resources/icons/instance_add.svg
+++ b/resources/icons/instance_add.svg
@@ -1,50 +1,46 @@
-
+
diff --git a/resources/icons/instance_remove.svg b/resources/icons/instance_remove.svg
index 466752ea8..7f9b4f7e1 100644
--- a/resources/icons/instance_remove.svg
+++ b/resources/icons/instance_remove.svg
@@ -1,49 +1,42 @@
-
+
diff --git a/resources/icons/paste.svg b/resources/icons/paste.svg
index 028ffb8ea..bcfe567de 100644
--- a/resources/icons/paste.svg
+++ b/resources/icons/paste.svg
@@ -1,27 +1,22 @@
-
+
diff --git a/resources/icons/prusa_slicer_logo.svg b/resources/icons/prusa_slicer_logo.svg
new file mode 100644
index 000000000..927c3e70b
--- /dev/null
+++ b/resources/icons/prusa_slicer_logo.svg
@@ -0,0 +1,5 @@
+
diff --git a/resources/icons/redo_toolbar.svg b/resources/icons/redo_toolbar.svg
index d005f8373..d2aca2cc7 100644
--- a/resources/icons/redo_toolbar.svg
+++ b/resources/icons/redo_toolbar.svg
@@ -1,13 +1,17 @@
-
+
diff --git a/resources/icons/remove.svg b/resources/icons/remove.svg
index acd21256c..1bb830d91 100644
--- a/resources/icons/remove.svg
+++ b/resources/icons/remove.svg
@@ -1,44 +1,60 @@
-
+
diff --git a/resources/icons/seam.svg b/resources/icons/seam.svg
index 119fb6afc..a7e7980cc 100644
--- a/resources/icons/seam.svg
+++ b/resources/icons/seam.svg
@@ -1,42 +1,35 @@
-
+
diff --git a/resources/icons/search_.svg b/resources/icons/search_.svg
index 679bb30f7..2985ceb56 100644
--- a/resources/icons/search_.svg
+++ b/resources/icons/search_.svg
@@ -1,4 +1,26 @@
-
\ No newline at end of file
+
+
+
diff --git a/resources/icons/search_blink.svg b/resources/icons/search_blink.svg
new file mode 100644
index 000000000..d005f8373
--- /dev/null
+++ b/resources/icons/search_blink.svg
@@ -0,0 +1,13 @@
+
+
+
diff --git a/resources/icons/settings.svg b/resources/icons/settings.svg
new file mode 100644
index 000000000..db5bf458d
--- /dev/null
+++ b/resources/icons/settings.svg
@@ -0,0 +1,68 @@
+
+
+
diff --git a/resources/icons/splashscreen-gcodeviewer.jpg b/resources/icons/splashscreen-gcodeviewer.jpg
new file mode 100644
index 000000000..f170f390c
Binary files /dev/null and b/resources/icons/splashscreen-gcodeviewer.jpg differ
diff --git a/resources/icons/splashscreen.jpg b/resources/icons/splashscreen.jpg
new file mode 100644
index 000000000..86c55f30f
Binary files /dev/null and b/resources/icons/splashscreen.jpg differ
diff --git a/resources/icons/split_objects.svg b/resources/icons/split_objects.svg
index a7ccc5df8..e822fd35a 100644
--- a/resources/icons/split_objects.svg
+++ b/resources/icons/split_objects.svg
@@ -1,19 +1,20 @@
-
+
diff --git a/resources/icons/split_parts.svg b/resources/icons/split_parts.svg
index 82a292770..5cfef0f33 100644
--- a/resources/icons/split_parts.svg
+++ b/resources/icons/split_parts.svg
@@ -1,18 +1,20 @@
-
+
diff --git a/resources/icons/undo_toolbar.svg b/resources/icons/undo_toolbar.svg
index 15778a7ba..2fc25bf73 100644
--- a/resources/icons/undo_toolbar.svg
+++ b/resources/icons/undo_toolbar.svg
@@ -1,13 +1,17 @@
-
+
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 0b0b3c0ee..ca57ca553 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -106,9 +106,9 @@ if (MINGW)
set_target_properties(PrusaSlicer PROPERTIES PREFIX "")
endif (MINGW)
-if (NOT WIN32)
- # Binary name on unix like systems (OSX, Linux)
- set_target_properties(PrusaSlicer PROPERTIES OUTPUT_NAME "prusa-slicer")
+if (NOT WIN32 AND NOT APPLE)
+ # Binary name on unix like systems (Linux, Unix)
+ set_target_properties(PrusaSlicer PROPERTIES OUTPUT_NAME "prusa-slicer")
endif ()
target_link_libraries(PrusaSlicer libslic3r cereal)
@@ -209,20 +209,34 @@ if (WIN32)
add_custom_target(PrusaSlicerDllsCopy ALL DEPENDS PrusaSlicer)
prusaslicer_copy_dlls(PrusaSlicerDllsCopy)
-elseif (XCODE)
- # Because of Debug/Release/etc. configurations (similar to MSVC) the slic3r binary is located in an extra level
- add_custom_command(TARGET PrusaSlicer POST_BUILD
- COMMAND ln -sfn "${SLIC3R_RESOURCES_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/resources"
- COMMENT "Symlinking the resources directory into the build tree"
- VERBATIM
- )
else ()
+ if (APPLE)
+ # On OSX, the name of the binary matches the name of the Application.
+ add_custom_command(TARGET PrusaSlicer POST_BUILD
+ COMMAND ln -sf PrusaSlicer prusa-slicer
+ COMMAND ln -sf PrusaSlicer prusa-gcodeviewer
+ COMMAND ln -sf PrusaSlicer PrusaGCodeViewer
+ WORKING_DIRECTORY "$"
+ COMMENT "Symlinking the G-code viewer to PrusaSlicer, symlinking to prusa-slicer and prusa-gcodeviewer"
+ VERBATIM)
+ else ()
+ add_custom_command(TARGET PrusaSlicer POST_BUILD
+ COMMAND ln -sf prusa-slicer prusa-gcodeviewer
+ WORKING_DIRECTORY "$"
+ COMMENT "Symlinking the G-code viewer to PrusaSlicer"
+ VERBATIM)
+ endif ()
+ if (XCODE)
+ # Because of Debug/Release/etc. configurations (similar to MSVC) the slic3r binary is located in an extra level
+ set(BIN_RESOURCES_DIR "${CMAKE_CURRENT_BINARY_DIR}/resources")
+ else ()
+ set(BIN_RESOURCES_DIR "${CMAKE_CURRENT_BINARY_DIR}/../resources")
+ endif ()
add_custom_command(TARGET PrusaSlicer POST_BUILD
- COMMAND ln -sfn "${SLIC3R_RESOURCES_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/../resources"
+ COMMAND ln -sfn "${SLIC3R_RESOURCES_DIR}" "${BIN_RESOURCES_DIR}"
COMMENT "Symlinking the resources directory into the build tree"
- VERBATIM
- )
-endif()
+ VERBATIM)
+endif ()
# Slic3r binary install target
if (WIN32)
diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp
index 2962f0cdf..a12ad8bb7 100644
--- a/src/PrusaSlicer.cpp
+++ b/src/PrusaSlicer.cpp
@@ -22,6 +22,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -101,8 +102,14 @@ int CLI::run(int argc, char **argv)
std::find(m_transforms.begin(), m_transforms.end(), "cut") == m_transforms.end() &&
std::find(m_transforms.begin(), m_transforms.end(), "cut_x") == m_transforms.end() &&
std::find(m_transforms.begin(), m_transforms.end(), "cut_y") == m_transforms.end();
- bool start_as_gcodeviewer = false;
-
+ bool start_as_gcodeviewer =
+#ifdef _WIN32
+ false;
+#else
+ // On Unix systems, the prusa-slicer binary may be symlinked to give the application a different meaning.
+ boost::algorithm::iends_with(boost::filesystem::path(argv[0]).filename().string(), "gcodeviewer");
+#endif // _WIN32
+
const std::vector &load_configs = m_config.option("load", true)->values;
// load config files supplied via --load
@@ -133,37 +140,57 @@ int CLI::run(int argc, char **argv)
m_print_config.apply(config);
}
- // Read input file(s) if any.
- for (const std::string &file : m_input_files) {
- if (! boost::filesystem::exists(file)) {
- boost::nowide::cerr << "No such file: " << file << std::endl;
- exit(1);
+#if ENABLE_GCODE_VIEWER
+ // are we starting as gcodeviewer ?
+ for (auto it = m_actions.begin(); it != m_actions.end(); ++it) {
+ if (*it == "gcodeviewer") {
+ start_gui = true;
+ start_as_gcodeviewer = true;
+ m_actions.erase(it);
+ break;
}
- Model model;
- try {
- // When loading an AMF or 3MF, config is imported as well, including the printer technology.
- DynamicPrintConfig config;
- model = Model::read_from_file(file, &config, true);
- PrinterTechnology other_printer_technology = Slic3r::printer_technology(config);
- if (printer_technology == ptUnknown) {
- printer_technology = other_printer_technology;
- } else if (printer_technology != other_printer_technology && other_printer_technology != ptUnknown) {
- boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl;
+ }
+#endif // ENABLE_GCODE_VIEWER
+
+ // Read input file(s) if any.
+#if ENABLE_GCODE_VIEWER
+ if (!start_as_gcodeviewer) {
+#endif // ENABLE_GCODE_VIEWER
+ for (const std::string& file : m_input_files) {
+ if (!boost::filesystem::exists(file)) {
+ boost::nowide::cerr << "No such file: " << file << std::endl;
+ exit(1);
+ }
+ Model model;
+ try {
+ // When loading an AMF or 3MF, config is imported as well, including the printer technology.
+ DynamicPrintConfig config;
+ model = Model::read_from_file(file, &config, true);
+ PrinterTechnology other_printer_technology = Slic3r::printer_technology(config);
+ if (printer_technology == ptUnknown) {
+ printer_technology = other_printer_technology;
+ }
+ else if (printer_technology != other_printer_technology && other_printer_technology != ptUnknown) {
+ boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl;
+ return 1;
+ }
+ // config is applied to m_print_config before the current m_config values.
+ config += std::move(m_print_config);
+ m_print_config = std::move(config);
+ }
+ catch (std::exception& e) {
+ boost::nowide::cerr << file << ": " << e.what() << std::endl;
return 1;
}
- // config is applied to m_print_config before the current m_config values.
- config += std::move(m_print_config);
- m_print_config = std::move(config);
- } catch (std::exception &e) {
- boost::nowide::cerr << file << ": " << e.what() << std::endl;
- return 1;
+ if (model.objects.empty()) {
+ boost::nowide::cerr << "Error: file is empty: " << file << std::endl;
+ continue;
+ }
+ m_models.push_back(model);
}
- if (model.objects.empty()) {
- boost::nowide::cerr << "Error: file is empty: " << file << std::endl;
- continue;
- }
- m_models.push_back(model);
+#if ENABLE_GCODE_VIEWER
}
+#endif // ENABLE_GCODE_VIEWER
// Apply command line options to a more specific DynamicPrintConfig which provides normalize()
// (command line options override --load files)
@@ -522,9 +549,11 @@ int CLI::run(int argc, char **argv)
<< " (" << print.total_extruded_volume()/1000 << "cm3)" << std::endl;
*/
}
+#if !ENABLE_GCODE_VIEWER
} else if (opt_key == "gcodeviewer") {
- start_gui = true;
+ start_gui = true;
start_as_gcodeviewer = true;
+#endif // !ENABLE_GCODE_VIEWER
} else {
boost::nowide::cerr << "error: option not supported yet: " << opt_key << std::endl;
return 1;
@@ -534,7 +563,11 @@ int CLI::run(int argc, char **argv)
if (start_gui) {
#ifdef SLIC3R_GUI
// #ifdef USE_WX
+#if ENABLE_GCODE_VIEWER
+ GUI::GUI_App* gui = new GUI::GUI_App(start_as_gcodeviewer ? GUI::GUI_App::EAppMode::GCodeViewer : GUI::GUI_App::EAppMode::Editor);
+#else
GUI::GUI_App *gui = new GUI::GUI_App();
+#endif // ENABLE_GCODE_VIEWER
bool gui_single_instance_setting = gui->app_config->get("single_instance") == "1";
if (Slic3r::instance_check(argc, argv, gui_single_instance_setting)) {
@@ -544,28 +577,42 @@ int CLI::run(int argc, char **argv)
// gui->autosave = m_config.opt_string("autosave");
GUI::GUI_App::SetInstance(gui);
+#if ENABLE_GCODE_VIEWER
+ gui->CallAfter([gui, this, &load_configs, start_as_gcodeviewer] {
+#else
gui->CallAfter([gui, this, &load_configs] {
+#endif // ENABLE_GCODE_VIEWER
if (!gui->initialized()) {
return;
}
+
+#if ENABLE_GCODE_VIEWER
+ if (start_as_gcodeviewer) {
+ if (!m_input_files.empty())
+ gui->plater()->load_gcode(wxString::FromUTF8(m_input_files[0].c_str()));
+ } else {
+#endif // ENABLE_GCODE_VIEWER_AS
#if 0
- // Load the cummulative config over the currently active profiles.
- //FIXME if multiple configs are loaded, only the last one will have an effect.
- // We need to decide what to do about loading of separate presets (just print preset, just filament preset etc).
- // As of now only the full configs are supported here.
- if (!m_print_config.empty())
- gui->mainframe->load_config(m_print_config);
+ // Load the cummulative config over the currently active profiles.
+ //FIXME if multiple configs are loaded, only the last one will have an effect.
+ // We need to decide what to do about loading of separate presets (just print preset, just filament preset etc).
+ // As of now only the full configs are supported here.
+ if (!m_print_config.empty())
+ gui->mainframe->load_config(m_print_config);
#endif
- if (! load_configs.empty())
- // Load the last config to give it a name at the UI. The name of the preset may be later
- // changed by loading an AMF or 3MF.
- //FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config.
- gui->mainframe->load_config_file(load_configs.back());
- // If loading a 3MF file, the config is loaded from the last one.
- if (! m_input_files.empty())
- gui->plater()->load_files(m_input_files, true, true);
- if (! m_extra_config.empty())
- gui->mainframe->load_config(m_extra_config);
+ if (!load_configs.empty())
+ // Load the last config to give it a name at the UI. The name of the preset may be later
+ // changed by loading an AMF or 3MF.
+ //FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config.
+ gui->mainframe->load_config_file(load_configs.back());
+ // If loading a 3MF file, the config is loaded from the last one.
+ if (!m_input_files.empty())
+ gui->plater()->load_files(m_input_files, true, true);
+ if (!m_extra_config.empty())
+ gui->mainframe->load_config(m_extra_config);
+#if ENABLE_GCODE_VIEWER
+ }
+#endif // ENABLE_GCODE_VIEWER
});
int result = wxEntry(argc, argv);
return result;
diff --git a/src/admesh/stl.h b/src/admesh/stl.h
index 9224b0459..e0f2865f0 100644
--- a/src/admesh/stl.h
+++ b/src/admesh/stl.h
@@ -255,18 +255,24 @@ extern void its_transform(indexed_triangle_set &its, T *trafo3x4)
}
template
-inline void its_transform(indexed_triangle_set &its, const Eigen::Transform& t)
+inline void its_transform(indexed_triangle_set &its, const Eigen::Transform& t, bool fix_left_handed = false)
{
//const Eigen::Matrix r = t.matrix().template block<3, 3>(0, 0);
for (stl_vertex &v : its.vertices)
v = (t * v.template cast()).template cast().eval();
+ if (fix_left_handed && t.matrix().block(0, 0, 3, 3).determinant() < 0.)
+ for (stl_triangle_vertex_indices &i : its.indices)
+ std::swap(i[0], i[1]);
}
template
-inline void its_transform(indexed_triangle_set &its, const Eigen::Matrix& m)
+inline void its_transform(indexed_triangle_set &its, const Eigen::Matrix& m, bool fix_left_handed = false)
{
- for (stl_vertex &v : its.vertices)
+ for (stl_vertex &v : its.vertices)
v = (m * v.template cast()).template cast().eval();
+ if (fix_left_handed && m.determinant() < 0.)
+ for (stl_triangle_vertex_indices &i : its.indices)
+ std::swap(i[0], i[1]);
}
extern void its_rotate_x(indexed_triangle_set &its, float angle);
diff --git a/src/libslic3r/AABBTreeIndirect.hpp b/src/libslic3r/AABBTreeIndirect.hpp
index ec9b14a7a..964133faa 100644
--- a/src/libslic3r/AABBTreeIndirect.hpp
+++ b/src/libslic3r/AABBTreeIndirect.hpp
@@ -283,7 +283,7 @@ namespace detail {
template
std::enable_if_t::value && std::is_same::value, bool>
- intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) {
+ intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v) {
return intersect_triangle1(const_cast(origin.data()), const_cast(dir.data()),
const_cast(v0.data()), const_cast(v1.data()), const_cast(v2.data()),
&t, &u, &v);
@@ -291,7 +291,7 @@ namespace detail {
template
std::enable_if_t::value && !std::is_same::value, bool>
- intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) {
+ intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v) {
using Vector = Eigen::Matrix;
Vector w0 = v0.template cast();
Vector w1 = v1.template cast();
@@ -302,7 +302,7 @@ namespace detail {
template
std::enable_if_t::value && std::is_same::value, bool>
- intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) {
+ intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v) {
using Vector = Eigen::Matrix;
Vector o = origin.template cast();
Vector d = dir.template cast();
@@ -311,7 +311,7 @@ namespace detail {
template
std::enable_if_t::value && ! std::is_same::value, bool>
- intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) {
+ intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v) {
using Vector = Eigen::Matrix;
Vector o = origin.template cast();
Vector d = dir.template cast();
@@ -692,6 +692,40 @@ inline typename VectorType::Scalar squared_distance_to_indexed_triangle_set(
detail::squared_distance_to_indexed_triangle_set_recursive(distancer, size_t(0), Scalar(0), std::numeric_limits::infinity(), hit_idx_out, hit_point_out);
}
+// Decides if exists some triangle in defined radius on a 3D indexed triangle set using a pre-built AABBTreeIndirect::Tree.
+// Closest point to triangle test will be performed with the accuracy of VectorType::Scalar
+// even if the triangle mesh and the AABB Tree are built with floats.
+// Returns true if exists some triangle in defined radius, false otherwise.
+template
+inline bool is_any_triangle_in_radius(
+ // Indexed triangle set - 3D vertices.
+ const std::vector &vertices,
+ // Indexed triangle set - triangular faces, references to vertices.
+ const std::vector &faces,
+ // AABBTreeIndirect::Tree over vertices & faces, bounding boxes built with the accuracy of vertices.
+ const TreeType &tree,
+ // Point to which the closest point on the indexed triangle set is searched for.
+ const VectorType &point,
+ // Maximum distance in which triangle is search for
+ typename VectorType::Scalar &max_distance)
+{
+ using Scalar = typename VectorType::Scalar;
+ auto distancer = detail::IndexedTriangleSetDistancer
+ { vertices, faces, tree, point };
+
+ size_t hit_idx;
+ VectorType hit_point = VectorType::Ones() * (std::nan(""));
+
+ if(tree.empty())
+ {
+ return false;
+ }
+
+ detail::squared_distance_to_indexed_triangle_set_recursive(distancer, size_t(0), Scalar(0), max_distance, hit_idx, hit_point);
+
+ return hit_point.allFinite();
+}
+
} // namespace AABBTreeIndirect
} // namespace Slic3r
diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp
index 8b41bd271..72c4fd0e9 100644
--- a/src/libslic3r/AppConfig.cpp
+++ b/src/libslic3r/AppConfig.cpp
@@ -1,6 +1,7 @@
#include "libslic3r/libslic3r.h"
#include "libslic3r/Utils.hpp"
#include "AppConfig.hpp"
+#include "Exception.hpp"
#include
#include
@@ -101,6 +102,9 @@ void AppConfig::set_defaults()
if (get("use_inches").empty())
set("use_inches", "0");
+ if (get("show_splash_screen").empty())
+ set("show_splash_screen", "1");
+
// Remove legacy window positions/sizes
erase("", "main_frame_maximized");
erase("", "main_frame_pos");
@@ -123,7 +127,7 @@ std::string AppConfig::load()
// ! But to avoid the use of _utf8 (related to use of wxWidgets)
// we will rethrow this exception from the place of load() call, if returned value wouldn't be empty
/*
- throw std::runtime_error(
+ throw Slic3r::RuntimeError(
_utf8(L("Error parsing PrusaSlicer config file, it is probably corrupted. "
"Try to manually delete the file to recover from the error. Your user profiles will not be affected.")) +
"\n\n" + AppConfig::config_path() + "\n\n" + ex.what());
@@ -179,6 +183,11 @@ std::string AppConfig::load()
void AppConfig::save()
{
+#if ENABLE_GCODE_VIEWER
+ if (!m_save_enabled)
+ return;
+#endif // ENABLE_GCODE_VIEWER
+
// The config is first written to a file with a PID suffix and then moved
// to avoid race conditions with multiple instances of Slic3r
const auto path = config_path();
diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp
index ffd1b9fdf..f22a6314a 100644
--- a/src/libslic3r/AppConfig.hpp
+++ b/src/libslic3r/AppConfig.hpp
@@ -18,6 +18,9 @@ public:
AppConfig() :
m_dirty(false),
m_orig_version(Semver::invalid()),
+#if ENABLE_GCODE_VIEWER
+ m_save_enabled(true),
+#endif // ENABLE_GCODE_VIEWER
m_legacy_datadir(false)
{
this->reset();
@@ -157,6 +160,10 @@ public:
bool get_mouse_device_swap_yz(const std::string& name, bool& swap) const
{ return get_3dmouse_device_numeric_value(name, "swap_yz", swap); }
+#if ENABLE_GCODE_VIEWER
+ void enable_save(bool enable) { m_save_enabled = enable; }
+#endif // ENABLE_GCODE_VIEWER
+
static const std::string SECTION_FILAMENTS;
static const std::string SECTION_MATERIALS;
@@ -183,6 +190,10 @@ private:
bool m_dirty;
// Original version found in the ini file before it was overwritten
Semver m_orig_version;
+#if ENABLE_GCODE_VIEWER
+ // Whether or not calls to save() should take effect
+ bool m_save_enabled;
+#endif // ENABLE_GCODE_VIEWER
// Whether the existing version is before system profiles & configuration updating
bool m_legacy_datadir;
};
diff --git a/src/libslic3r/BoundingBox.cpp b/src/libslic3r/BoundingBox.cpp
index e3f939509..eb4e042a0 100644
--- a/src/libslic3r/BoundingBox.cpp
+++ b/src/libslic3r/BoundingBox.cpp
@@ -75,6 +75,7 @@ BoundingBoxBase::merge(const PointClass &point)
}
}
template void BoundingBoxBase::merge(const Point &point);
+template void BoundingBoxBase::merge(const Vec2f &point);
template void BoundingBoxBase::merge(const Vec2d &point);
template void
@@ -101,6 +102,7 @@ BoundingBoxBase::merge(const BoundingBoxBase &bb)
}
}
template void BoundingBoxBase::merge(const BoundingBoxBase &bb);
+template void BoundingBoxBase::merge(const BoundingBoxBase &bb);
template void BoundingBoxBase::merge(const BoundingBoxBase &bb);
template void
@@ -115,6 +117,7 @@ BoundingBox3Base::merge(const PointClass &point)
this->defined = true;
}
}
+template void BoundingBox3Base::merge(const Vec3f &point);
template void BoundingBox3Base::merge(const Vec3d &point);
template void
@@ -147,6 +150,7 @@ BoundingBoxBase::size() const
return PointClass(this->max(0) - this->min(0), this->max(1) - this->min(1));
}
template Point BoundingBoxBase::size() const;
+template Vec2f BoundingBoxBase::size() const;
template Vec2d BoundingBoxBase::size() const;
template PointClass
@@ -154,6 +158,7 @@ BoundingBox3Base::size() const
{
return PointClass(this->max(0) - this->min(0), this->max(1) - this->min(1), this->max(2) - this->min(2));
}
+template Vec3f BoundingBox3Base::size() const;
template Vec3d BoundingBox3Base::size() const;
template double BoundingBoxBase::radius() const
@@ -200,6 +205,7 @@ BoundingBoxBase::center() const
return (this->min + this->max) / 2;
}
template Point BoundingBoxBase::center() const;
+template Vec2f BoundingBoxBase::center() const;
template Vec2d BoundingBoxBase::center() const;
template PointClass
@@ -207,6 +213,7 @@ BoundingBox3Base::center() const
{
return (this->min + this->max) / 2;
}
+template Vec3f BoundingBox3Base::center() const;
template Vec3d BoundingBox3Base::center() const;
template coordf_t
@@ -215,6 +222,7 @@ BoundingBox3Base::max_size() const
PointClass s = size();
return std::max(s(0), std::max(s(1), s(2)));
}
+template coordf_t BoundingBox3Base::max_size() const;
template coordf_t BoundingBox3Base::max_size() const;
// Align a coordinate to a grid. The coordinate may be negative,
diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp
index 08f01d8d8..065476cb2 100644
--- a/src/libslic3r/BoundingBox.hpp
+++ b/src/libslic3r/BoundingBox.hpp
@@ -2,6 +2,7 @@
#define slic3r_BoundingBox_hpp_
#include "libslic3r.h"
+#include "Exception.hpp"
#include "Point.hpp"
#include "Polygon.hpp"
@@ -18,11 +19,13 @@ public:
BoundingBoxBase() : min(PointClass::Zero()), max(PointClass::Zero()), defined(false) {}
BoundingBoxBase(const PointClass &pmin, const PointClass &pmax) :
min(pmin), max(pmax), defined(pmin(0) < pmax(0) && pmin(1) < pmax(1)) {}
+ BoundingBoxBase(const PointClass &p1, const PointClass &p2, const PointClass &p3) :
+ min(p1), max(p1), defined(false) { merge(p2); merge(p3); }
BoundingBoxBase(const std::vector& points) : min(PointClass::Zero()), max(PointClass::Zero())
{
if (points.empty()) {
this->defined = false;
- // throw std::invalid_argument("Empty point set supplied to BoundingBoxBase constructor");
+ // throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBoxBase constructor");
} else {
typename std::vector::const_iterator it = points.begin();
this->min = *it;
@@ -65,10 +68,12 @@ public:
BoundingBox3Base(const PointClass &pmin, const PointClass &pmax) :
BoundingBoxBase(pmin, pmax)
{ if (pmin(2) >= pmax(2)) BoundingBoxBase::defined = false; }
+ BoundingBox3Base(const PointClass &p1, const PointClass &p2, const PointClass &p3) :
+ BoundingBoxBase(p1, p1) { merge(p2); merge(p3); }
BoundingBox3Base(const std::vector& points)
{
if (points.empty())
- throw std::invalid_argument("Empty point set supplied to BoundingBox3Base constructor");
+ throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBox3Base constructor");
typename std::vector::const_iterator it = points.begin();
this->min = *it;
this->max = *it;
@@ -109,24 +114,32 @@ extern template void BoundingBoxBase::scale(double factor);
extern template void BoundingBoxBase::offset(coordf_t delta);
extern template void BoundingBoxBase::offset(coordf_t delta);
extern template void BoundingBoxBase::merge(const Point &point);
+extern template void BoundingBoxBase::merge(const Vec2f &point);
extern template void BoundingBoxBase::merge(const Vec2d &point);
extern template void BoundingBoxBase::merge(const Points &points);
extern template void BoundingBoxBase::merge(const Pointfs &points);
extern template void BoundingBoxBase::merge(const BoundingBoxBase &bb);
+extern template void BoundingBoxBase::merge(const BoundingBoxBase &bb);
extern template void BoundingBoxBase::merge(const BoundingBoxBase &bb);
extern template Point BoundingBoxBase::size() const;
+extern template Vec2f BoundingBoxBase::size() const;
extern template Vec2d BoundingBoxBase::size() const;
extern template double BoundingBoxBase::radius() const;
extern template double BoundingBoxBase::radius() const;
extern template Point BoundingBoxBase::center() const;
+extern template Vec2f BoundingBoxBase::center() const;
extern template Vec2d BoundingBoxBase::center() const;
+extern template void BoundingBox3Base::merge(const Vec3f &point);
extern template void BoundingBox3Base::merge(const Vec3d &point);
extern template void BoundingBox3Base::merge(const Pointf3s &points);
extern template void BoundingBox3Base::merge(const BoundingBox3Base &bb);
+extern template Vec3f BoundingBox3Base::size() const;
extern template Vec3d BoundingBox3Base::size() const;
extern template double BoundingBox3Base::radius() const;
extern template void BoundingBox3Base::offset(coordf_t delta);
+extern template Vec3f BoundingBox3Base::center() const;
extern template Vec3d BoundingBox3Base::center() const;
+extern template coordf_t BoundingBox3Base::max_size() const;
extern template coordf_t BoundingBox3Base::max_size() const;
class BoundingBox : public BoundingBoxBase
diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt
index cde2e9eab..ee41248f3 100644
--- a/src/libslic3r/CMakeLists.txt
+++ b/src/libslic3r/CMakeLists.txt
@@ -46,6 +46,8 @@ add_library(libslic3r STATIC
Fill/Fill.hpp
Fill/Fill3DHoneycomb.cpp
Fill/Fill3DHoneycomb.hpp
+ Fill/FillAdaptive.cpp
+ Fill/FillAdaptive.hpp
Fill/FillBase.cpp
Fill/FillBase.hpp
Fill/FillConcentric.cpp
@@ -215,7 +217,9 @@ add_library(libslic3r STATIC
SimplifyMeshImpl.hpp
SimplifyMesh.cpp
MarchingSquares.hpp
- Optimizer.hpp
+ Optimize/Optimizer.hpp
+ Optimize/NLoptOptimizer.hpp
+ Optimize/BruteforceOptimizer.hpp
${OpenVDBUtils_SOURCES}
SLA/Pad.hpp
SLA/Pad.cpp
diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp
index f3f365b47..25ef93430 100644
--- a/src/libslic3r/Config.cpp
+++ b/src/libslic3r/Config.cpp
@@ -5,7 +5,6 @@
#include
#include
#include
-#include // std::runtime_error
#include
#include
#include
@@ -218,7 +217,7 @@ ConfigOption* ConfigOptionDef::create_empty_option() const
case coInts: return new ConfigOptionIntsNullable();
case coPercents: return new ConfigOptionPercentsNullable();
case coBools: return new ConfigOptionBoolsNullable();
- default: throw std::runtime_error(std::string("Unknown option type for nullable option ") + this->label);
+ default: throw Slic3r::RuntimeError(std::string("Unknown option type for nullable option ") + this->label);
}
} else {
switch (this->type) {
@@ -238,7 +237,7 @@ ConfigOption* ConfigOptionDef::create_empty_option() const
case coBool: return new ConfigOptionBool();
case coBools: return new ConfigOptionBools();
case coEnum: return new ConfigOptionEnumGeneric(this->enum_keys_map);
- default: throw std::runtime_error(std::string("Unknown option type for option ") + this->label);
+ default: throw Slic3r::RuntimeError(std::string("Unknown option type for option ") + this->label);
}
}
}
@@ -535,7 +534,7 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key) const
return opt_def->ratio_over.empty() ? 0. :
static_cast(raw_opt)->get_abs_value(this->get_abs_value(opt_def->ratio_over));
}
- throw std::runtime_error("ConfigBase::get_abs_value(): Not a valid option type for get_abs_value()");
+ throw Slic3r::RuntimeError("ConfigBase::get_abs_value(): Not a valid option type for get_abs_value()");
}
// Return an absolute value of a possibly relative config variable.
@@ -546,7 +545,7 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key, double rati
const ConfigOption *raw_opt = this->option(opt_key);
assert(raw_opt != nullptr);
if (raw_opt->type() != coFloatOrPercent)
- throw std::runtime_error("ConfigBase::get_abs_value(): opt_key is not of coFloatOrPercent");
+ throw Slic3r::RuntimeError("ConfigBase::get_abs_value(): opt_key is not of coFloatOrPercent");
// Compute absolute value.
return static_cast(raw_opt)->get_abs_value(ratio_over);
}
@@ -609,7 +608,7 @@ void ConfigBase::load_from_gcode_file(const std::string &file)
std::getline(ifs, firstline);
if (strncmp(slic3r_gcode_header, firstline.c_str(), strlen(slic3r_gcode_header)) != 0 &&
strncmp(prusaslicer_gcode_header, firstline.c_str(), strlen(prusaslicer_gcode_header)) != 0)
- throw std::runtime_error("Not a PrusaSlicer / Slic3r PE generated g-code.");
+ throw Slic3r::RuntimeError("Not a PrusaSlicer / Slic3r PE generated g-code.");
}
ifs.seekg(0, ifs.end);
auto file_length = ifs.tellg();
@@ -621,7 +620,7 @@ void ConfigBase::load_from_gcode_file(const std::string &file)
size_t key_value_pairs = load_from_gcode_string(data.data());
if (key_value_pairs < 80)
- throw std::runtime_error(format("Suspiciously low number of configuration values extracted from %1%: %2%", file, key_value_pairs));
+ throw Slic3r::RuntimeError(format("Suspiciously low number of configuration values extracted from %1%: %2%", file, key_value_pairs));
}
// Load the config keys from the given string.
@@ -750,7 +749,7 @@ ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool cre
throw NoDefinitionException(opt_key);
const ConfigOptionDef *optdef = def->get(opt_key);
if (optdef == nullptr)
-// throw std::runtime_error(std::string("Invalid option name: ") + opt_key);
+// throw Slic3r::RuntimeError(std::string("Invalid option name: ") + opt_key);
// Let the parent decide what to do if the opt_key is not defined by this->def().
return nullptr;
ConfigOption *opt = optdef->create_default_option();
diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp
index 87e020898..28b28b405 100644
--- a/src/libslic3r/Config.hpp
+++ b/src/libslic3r/Config.hpp
@@ -13,6 +13,7 @@
#include
#include "libslic3r.h"
#include "clonable_ptr.hpp"
+#include "Exception.hpp"
#include "Point.hpp"
#include
@@ -34,31 +35,31 @@ extern bool unescape_string_cstyle(const std::string &str, std::string &
extern bool unescape_strings_cstyle(const std::string &str, std::vector &out);
/// Specialization of std::exception to indicate that an unknown config option has been encountered.
-class UnknownOptionException : public std::runtime_error {
+class UnknownOptionException : public Slic3r::RuntimeError {
public:
UnknownOptionException() :
- std::runtime_error("Unknown option exception") {}
+ Slic3r::RuntimeError("Unknown option exception") {}
UnknownOptionException(const std::string &opt_key) :
- std::runtime_error(std::string("Unknown option exception: ") + opt_key) {}
+ Slic3r::RuntimeError(std::string("Unknown option exception: ") + opt_key) {}
};
/// Indicate that the ConfigBase derived class does not provide config definition (the method def() returns null).
-class NoDefinitionException : public std::runtime_error
+class NoDefinitionException : public Slic3r::RuntimeError
{
public:
NoDefinitionException() :
- std::runtime_error("No definition exception") {}
+ Slic3r::RuntimeError("No definition exception") {}
NoDefinitionException(const std::string &opt_key) :
- std::runtime_error(std::string("No definition exception: ") + opt_key) {}
+ Slic3r::RuntimeError(std::string("No definition exception: ") + opt_key) {}
};
/// Indicate that an unsupported accessor was called on a config option.
-class BadOptionTypeException : public std::runtime_error
+class BadOptionTypeException : public Slic3r::RuntimeError
{
public:
- BadOptionTypeException() : std::runtime_error("Bad option type exception") {}
- BadOptionTypeException(const std::string &message) : std::runtime_error(message) {}
- BadOptionTypeException(const char* message) : std::runtime_error(message) {}
+ BadOptionTypeException() : Slic3r::RuntimeError("Bad option type exception") {}
+ BadOptionTypeException(const std::string &message) : Slic3r::RuntimeError(message) {}
+ BadOptionTypeException(const char* message) : Slic3r::RuntimeError(message) {}
};
// Type of a configuration value.
@@ -167,7 +168,7 @@ public:
void set(const ConfigOption *rhs) override
{
if (rhs->type() != this->type())
- throw std::runtime_error("ConfigOptionSingle: Assigning an incompatible type");
+ throw Slic3r::RuntimeError("ConfigOptionSingle: Assigning an incompatible type");
assert(dynamic_cast*>(rhs));
this->value = static_cast*>(rhs)->value;
}
@@ -175,7 +176,7 @@ public:
bool operator==(const ConfigOption &rhs) const override
{
if (rhs.type() != this->type())
- throw std::runtime_error("ConfigOptionSingle: Comparing incompatible types");
+ throw Slic3r::RuntimeError("ConfigOptionSingle: Comparing incompatible types");
assert(dynamic_cast*>(&rhs));
return this->value == static_cast*>(&rhs)->value;
}
@@ -239,7 +240,7 @@ public:
void set(const ConfigOption *rhs) override
{
if (rhs->type() != this->type())
- throw std::runtime_error("ConfigOptionVector: Assigning an incompatible type");
+ throw Slic3r::RuntimeError("ConfigOptionVector: Assigning an incompatible type");
assert(dynamic_cast*>(rhs));
this->values = static_cast*>(rhs)->values;
}
@@ -256,12 +257,12 @@ public:
if (opt->type() == this->type()) {
auto other = static_cast*>(opt);
if (other->values.empty())
- throw std::runtime_error("ConfigOptionVector::set(): Assigning from an empty vector");
+ throw Slic3r::RuntimeError("ConfigOptionVector::set(): Assigning from an empty vector");
this->values.emplace_back(other->values.front());
} else if (opt->type() == this->scalar_type())
this->values.emplace_back(static_cast*>(opt)->value);
else
- throw std::runtime_error("ConfigOptionVector::set():: Assigning an incompatible type");
+ throw Slic3r::RuntimeError("ConfigOptionVector::set():: Assigning an incompatible type");
}
}
@@ -280,12 +281,12 @@ public:
// Assign the first value of the rhs vector.
auto other = static_cast*>(rhs);
if (other->values.empty())
- throw std::runtime_error("ConfigOptionVector::set_at(): Assigning from an empty vector");
+ throw Slic3r::RuntimeError("ConfigOptionVector::set_at(): Assigning from an empty vector");
this->values[i] = other->get_at(j);
} else if (rhs->type() == this->scalar_type())
this->values[i] = static_cast*>(rhs)->value;
else
- throw std::runtime_error("ConfigOptionVector::set_at(): Assigning an incompatible type");
+ throw Slic3r::RuntimeError("ConfigOptionVector::set_at(): Assigning an incompatible type");
}
const T& get_at(size_t i) const
@@ -310,9 +311,9 @@ public:
else if (n > this->values.size()) {
if (this->values.empty()) {
if (opt_default == nullptr)
- throw std::runtime_error("ConfigOptionVector::resize(): No default value provided.");
+ throw Slic3r::RuntimeError("ConfigOptionVector::resize(): No default value provided.");
if (opt_default->type() != this->type())
- throw std::runtime_error("ConfigOptionVector::resize(): Extending with an incompatible type.");
+ throw Slic3r::RuntimeError("ConfigOptionVector::resize(): Extending with an incompatible type.");
this->values.resize(n, static_cast*>(opt_default)->values.front());
} else {
// Resize by duplicating the last value.
@@ -329,7 +330,7 @@ public:
bool operator==(const ConfigOption &rhs) const override
{
if (rhs.type() != this->type())
- throw std::runtime_error("ConfigOptionVector: Comparing incompatible types");
+ throw Slic3r::RuntimeError("ConfigOptionVector: Comparing incompatible types");
assert(dynamic_cast*>(&rhs));
return this->values == static_cast*>(&rhs)->values;
}
@@ -341,9 +342,9 @@ public:
// An option overrides another option if it is not nil and not equal.
bool overriden_by(const ConfigOption *rhs) const override {
if (this->nullable())
- throw std::runtime_error("Cannot override a nullable ConfigOption.");
+ throw Slic3r::RuntimeError("Cannot override a nullable ConfigOption.");
if (rhs->type() != this->type())
- throw std::runtime_error("ConfigOptionVector.overriden_by() applied to different types.");
+ throw Slic3r::RuntimeError("ConfigOptionVector.overriden_by() applied to different types.");
auto rhs_vec = static_cast*>(rhs);
if (! rhs->nullable())
// Overridding a non-nullable object with another non-nullable object.
@@ -361,9 +362,9 @@ public:
// Apply an override option, possibly a nullable one.
bool apply_override(const ConfigOption *rhs) override {
if (this->nullable())
- throw std::runtime_error("Cannot override a nullable ConfigOption.");
+ throw Slic3r::RuntimeError("Cannot override a nullable ConfigOption.");
if (rhs->type() != this->type())
- throw std::runtime_error("ConfigOptionVector.apply_override() applied to different types.");
+ throw Slic3r::RuntimeError("ConfigOptionVector.apply_override() applied to different types.");
auto rhs_vec = static_cast*>(rhs);
if (! rhs->nullable()) {
// Overridding a non-nullable object with another non-nullable object.
@@ -452,7 +453,7 @@ public:
bool operator==(const ConfigOptionFloatsTempl &rhs) const { return vectors_equal(this->values, rhs.values); }
bool operator==(const ConfigOption &rhs) const override {
if (rhs.type() != this->type())
- throw std::runtime_error("ConfigOptionFloatsTempl: Comparing incompatible types");
+ throw Slic3r::RuntimeError("ConfigOptionFloatsTempl: Comparing incompatible types");
assert(dynamic_cast*>(&rhs));
return vectors_equal(this->values, static_cast*>(&rhs)->values);
}
@@ -499,7 +500,7 @@ public:
if (NULLABLE)
this->values.push_back(nil_value());
else
- throw std::runtime_error("Deserializing nil into a non-nullable object");
+ throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object");
} else {
std::istringstream iss(item_str);
double value;
@@ -524,9 +525,9 @@ protected:
if (NULLABLE)
ss << "nil";
else
- throw std::runtime_error("Serializing NaN");
+ throw Slic3r::RuntimeError("Serializing NaN");
} else
- throw std::runtime_error("Serializing invalid number");
+ throw Slic3r::RuntimeError("Serializing invalid number");
}
static bool vectors_equal(const std::vector &v1, const std::vector &v2) {
if (NULLABLE) {
@@ -645,7 +646,7 @@ public:
if (NULLABLE)
this->values.push_back(nil_value());
else
- throw std::runtime_error("Deserializing nil into a non-nullable object");
+ throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object");
} else {
std::istringstream iss(item_str);
int value;
@@ -662,7 +663,7 @@ private:
if (NULLABLE)
ss << "nil";
else
- throw std::runtime_error("Serializing NaN");
+ throw Slic3r::RuntimeError("Serializing NaN");
} else
ss << v;
}
@@ -847,7 +848,7 @@ public:
bool operator==(const ConfigOption &rhs) const override
{
if (rhs.type() != this->type())
- throw std::runtime_error("ConfigOptionFloatOrPercent: Comparing incompatible types");
+ throw Slic3r::RuntimeError("ConfigOptionFloatOrPercent: Comparing incompatible types");
assert(dynamic_cast(&rhs));
return *this == *static_cast(&rhs);
}
@@ -858,7 +859,7 @@ public:
void set(const ConfigOption *rhs) override {
if (rhs->type() != this->type())
- throw std::runtime_error("ConfigOptionFloatOrPercent: Assigning an incompatible type");
+ throw Slic3r::RuntimeError("ConfigOptionFloatOrPercent: Assigning an incompatible type");
assert(dynamic_cast(rhs));
*this = *static_cast(rhs);
}
@@ -1126,7 +1127,7 @@ public:
if (NULLABLE)
this->values.push_back(nil_value());
else
- throw std::runtime_error("Deserializing nil into a non-nullable object");
+ throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object");
} else
this->values.push_back(item_str.compare("1") == 0);
}
@@ -1139,7 +1140,7 @@ protected:
if (NULLABLE)
ss << "nil";
else
- throw std::runtime_error("Serializing NaN");
+ throw Slic3r::RuntimeError("Serializing NaN");
} else
ss << (v ? "1" : "0");
}
@@ -1175,14 +1176,14 @@ public:
bool operator==(const ConfigOption &rhs) const override
{
if (rhs.type() != this->type())
- throw std::runtime_error("ConfigOptionEnum: Comparing incompatible types");
+ throw Slic3r::RuntimeError("ConfigOptionEnum: Comparing incompatible types");
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum
return this->value == (T)rhs.getInt();
}
void set(const ConfigOption *rhs) override {
if (rhs->type() != this->type())
- throw std::runtime_error("ConfigOptionEnum: Assigning an incompatible type");
+ throw Slic3r::RuntimeError("ConfigOptionEnum: Assigning an incompatible type");
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum
this->value = (T)rhs->getInt();
}
@@ -1259,14 +1260,14 @@ public:
bool operator==(const ConfigOption &rhs) const override
{
if (rhs.type() != this->type())
- throw std::runtime_error("ConfigOptionEnumGeneric: Comparing incompatible types");
+ throw Slic3r::RuntimeError("ConfigOptionEnumGeneric: Comparing incompatible types");
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum
return this->value == rhs.getInt();
}
void set(const ConfigOption *rhs) override {
if (rhs->type() != this->type())
- throw std::runtime_error("ConfigOptionEnumGeneric: Assigning an incompatible type");
+ throw Slic3r::RuntimeError("ConfigOptionEnumGeneric: Assigning an incompatible type");
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum
this->value = rhs->getInt();
}
@@ -1321,7 +1322,7 @@ public:
case coInts: { auto opt = new ConfigOptionIntsNullable(); archive(*opt); return opt; }
case coPercents: { auto opt = new ConfigOptionPercentsNullable();archive(*opt); return opt; }
case coBools: { auto opt = new ConfigOptionBoolsNullable(); archive(*opt); return opt; }
- default: throw std::runtime_error(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key);
+ default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key);
}
} else {
switch (this->type) {
@@ -1340,7 +1341,7 @@ public:
case coBool: { auto opt = new ConfigOptionBool(); archive(*opt); return opt; }
case coBools: { auto opt = new ConfigOptionBools(); archive(*opt); return opt; }
case coEnum: { auto opt = new ConfigOptionEnumGeneric(this->enum_keys_map); archive(*opt); return opt; }
- default: throw std::runtime_error(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key);
+ default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key);
}
}
}
@@ -1352,7 +1353,7 @@ public:
case coInts: archive(*static_cast(opt)); break;
case coPercents: archive(*static_cast(opt));break;
case coBools: archive(*static_cast(opt)); break;
- default: throw std::runtime_error(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key);
+ default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key);
}
} else {
switch (this->type) {
@@ -1371,7 +1372,7 @@ public:
case coBool: archive(*static_cast(opt)); break;
case coBools: archive(*static_cast(opt)); break;
case coEnum: archive(*static_cast(opt)); break;
- default: throw std::runtime_error(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key);
+ default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key);
}
}
// Make the compiler happy, shut up the warnings.
diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp
index daaab4755..5bdd5055e 100644
--- a/src/libslic3r/ExPolygon.cpp
+++ b/src/libslic3r/ExPolygon.cpp
@@ -1,5 +1,6 @@
#include "BoundingBox.hpp"
#include "ExPolygon.hpp"
+#include "Exception.hpp"
#include "Geometry.hpp"
#include "Polygon.hpp"
#include "Line.hpp"
@@ -435,7 +436,7 @@ void ExPolygon::triangulate_pp(Polygons* polygons) const
std::list output;
int res = TPPLPartition().Triangulate_MONO(&input, &output);
if (res != 1)
- throw std::runtime_error("Triangulation failed");
+ throw Slic3r::RuntimeError("Triangulation failed");
// convert output polygons
for (std::list::iterator poly = output.begin(); poly != output.end(); ++poly) {
@@ -548,7 +549,7 @@ void ExPolygon::triangulate_pp(Points *triangles) const
int res = TPPLPartition().Triangulate_MONO(&input, &output);
// int TPPLPartition::Triangulate_EC(TPPLPolyList *inpolys, TPPLPolyList *triangles) {
if (res != 1)
- throw std::runtime_error("Triangulation failed");
+ throw Slic3r::RuntimeError("Triangulation failed");
*triangles = polypartition_output_to_triangles(output);
}
@@ -591,7 +592,7 @@ void ExPolygon::triangulate_p2t(Polygons* polygons) const
}
polygons->push_back(p);
}
- } catch (const std::runtime_error & /* err */) {
+ } catch (const Slic3r::RuntimeError & /* err */) {
assert(false);
// just ignore, don't triangulate
}
diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp
index 4aad3603f..373853f97 100644
--- a/src/libslic3r/ExPolygon.hpp
+++ b/src/libslic3r/ExPolygon.hpp
@@ -333,6 +333,14 @@ extern std::list expoly_to_polypartition_input(const ExPolygons &expp)
extern std::list expoly_to_polypartition_input(const ExPolygon &ex);
extern std::vector polypartition_output_to_triangles(const std::list &output);
+inline double area(const ExPolygons &polys)
+{
+ double s = 0.;
+ for (auto &p : polys) s += p.area();
+
+ return s;
+}
+
} // namespace Slic3r
// start Boost
diff --git a/src/libslic3r/Exception.hpp b/src/libslic3r/Exception.hpp
new file mode 100644
index 000000000..8ec9f20c8
--- /dev/null
+++ b/src/libslic3r/Exception.hpp
@@ -0,0 +1,28 @@
+#ifndef _libslic3r_Exception_h_
+#define _libslic3r_Exception_h_
+
+#include
+
+namespace Slic3r {
+
+// PrusaSlicer's own exception hierarchy is derived from std::runtime_error.
+// Base for Slicer's own exceptions.
+class Exception : public std::runtime_error { using std::runtime_error::runtime_error; };
+#define SLIC3R_DERIVE_EXCEPTION(DERIVED_EXCEPTION, PARENT_EXCEPTION) \
+ class DERIVED_EXCEPTION : public PARENT_EXCEPTION { using PARENT_EXCEPTION::PARENT_EXCEPTION; }
+// Critical exception produced by Slicer, such exception shall never propagate up to the UI thread.
+// If that happens, an ugly fat message box with an ugly fat exclamation mark is displayed.
+SLIC3R_DERIVE_EXCEPTION(CriticalException, Exception);
+SLIC3R_DERIVE_EXCEPTION(RuntimeError, CriticalException);
+SLIC3R_DERIVE_EXCEPTION(LogicError, CriticalException);
+SLIC3R_DERIVE_EXCEPTION(InvalidArgument, LogicError);
+SLIC3R_DERIVE_EXCEPTION(OutOfRange, LogicError);
+SLIC3R_DERIVE_EXCEPTION(IOError, CriticalException);
+SLIC3R_DERIVE_EXCEPTION(FileIOError, IOError);
+// Runtime exception produced by Slicer. Such exception cancels the slicing process and it shall be shown in notifications.
+SLIC3R_DERIVE_EXCEPTION(SlicingError, Exception);
+#undef SLIC3R_DERIVE_EXCEPTION
+
+} // namespace Slic3r
+
+#endif // _libslic3r_Exception_h_
diff --git a/src/libslic3r/ExtrusionEntityCollection.hpp b/src/libslic3r/ExtrusionEntityCollection.hpp
index dfece6949..5e40ab32e 100644
--- a/src/libslic3r/ExtrusionEntityCollection.hpp
+++ b/src/libslic3r/ExtrusionEntityCollection.hpp
@@ -2,6 +2,7 @@
#define slic3r_ExtrusionEntityCollection_hpp_
#include "libslic3r.h"
+#include "Exception.hpp"
#include "ExtrusionEntity.hpp"
namespace Slic3r {
@@ -107,7 +108,7 @@ public:
// Following methods shall never be called on an ExtrusionEntityCollection.
Polyline as_polyline() const override {
- throw std::runtime_error("Calling as_polyline() on a ExtrusionEntityCollection");
+ throw Slic3r::RuntimeError("Calling as_polyline() on a ExtrusionEntityCollection");
return Polyline();
};
@@ -117,7 +118,7 @@ public:
}
double length() const override {
- throw std::runtime_error("Calling length() on a ExtrusionEntityCollection");
+ throw Slic3r::RuntimeError("Calling length() on a ExtrusionEntityCollection");
return 0.;
}
};
diff --git a/src/libslic3r/FileParserError.hpp b/src/libslic3r/FileParserError.hpp
index 3f560fa4f..b7e63d84e 100644
--- a/src/libslic3r/FileParserError.hpp
+++ b/src/libslic3r/FileParserError.hpp
@@ -10,14 +10,14 @@
namespace Slic3r {
// Generic file parser error, mostly copied from boost::property_tree::file_parser_error
-class file_parser_error: public std::runtime_error
+class file_parser_error: public Slic3r::RuntimeError
{
public:
file_parser_error(const std::string &msg, const std::string &file, unsigned long line = 0) :
- std::runtime_error(format_what(msg, file, line)),
+ Slic3r::RuntimeError(format_what(msg, file, line)),
m_message(msg), m_filename(file), m_line(line) {}
file_parser_error(const std::string &msg, const boost::filesystem::path &file, unsigned long line = 0) :
- std::runtime_error(format_what(msg, file.string(), line)),
+ Slic3r::RuntimeError(format_what(msg, file.string(), line)),
m_message(msg), m_filename(file.string()), m_line(line) {}
// gcc 3.4.2 complains about lack of throw specifier on compiler
// generated dtor
@@ -35,7 +35,7 @@ private:
std::string m_filename;
unsigned long m_line;
- // Format error message to be returned by std::runtime_error::what()
+ // Format error message to be returned by Slic3r::RuntimeError::what()
static std::string format_what(const std::string &msg, const std::string &file, unsigned long l)
{
std::stringstream stream;
diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp
index 3c16527f0..03aa798dc 100644
--- a/src/libslic3r/Fill/Fill.cpp
+++ b/src/libslic3r/Fill/Fill.cpp
@@ -318,7 +318,7 @@ void export_group_fills_to_svg(const char *path, const std::vector
#endif
// friend to Layer
-void Layer::make_fills()
+void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree)
{
for (LayerRegion *layerm : m_regions)
layerm->fills.clear();
@@ -345,6 +345,7 @@ void Layer::make_fills()
f->layer_id = this->id();
f->z = this->print_z;
f->angle = surface_fill.params.angle;
+ f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree;
// calculate flow spacing for infill pattern generation
bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.flow.bridge;
diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp
new file mode 100644
index 000000000..aed71aa72
--- /dev/null
+++ b/src/libslic3r/Fill/FillAdaptive.cpp
@@ -0,0 +1,720 @@
+#include "../ClipperUtils.hpp"
+#include "../ExPolygon.hpp"
+#include "../Surface.hpp"
+#include "../Geometry.hpp"
+#include "../Layer.hpp"
+#include "../Print.hpp"
+#include "../ShortestPath.hpp"
+
+#include "FillAdaptive.hpp"
+
+// for indexed_triangle_set
+#include
+
+#include
+#include
+
+// Boost pool: Don't use mutexes to synchronize memory allocation.
+#define BOOST_POOL_NO_MT
+#include
+
+namespace Slic3r {
+namespace FillAdaptive {
+
+// Derived from https://github.com/juj/MathGeoLib/blob/master/src/Geometry/Triangle.cpp
+// The AABB-Triangle test implementation is based on the pseudo-code in
+// Christer Ericson's Real-Time Collision Detection, pp. 169-172. It is
+// practically a standard SAT test.
+//
+// Original MathGeoLib benchmark:
+// Best: 17.282 nsecs / 46.496 ticks, Avg: 17.804 nsecs, Worst: 18.434 nsecs
+//
+//FIXME Vojtech: The MathGeoLib contains a vectorized implementation.
+template
+bool triangle_AABB_intersects(const Vector &a, const Vector &b, const Vector &c, const BoundingBoxBase &aabb)
+{
+ using Scalar = typename Vector::Scalar;
+
+ Vector tMin = a.cwiseMin(b.cwiseMin(c));
+ Vector tMax = a.cwiseMax(b.cwiseMax(c));
+
+ if (tMin.x() >= aabb.max.x() || tMax.x() <= aabb.min.x()
+ || tMin.y() >= aabb.max.y() || tMax.y() <= aabb.min.y()
+ || tMin.z() >= aabb.max.z() || tMax.z() <= aabb.min.z())
+ return false;
+
+ Vector center = (aabb.min + aabb.max) * 0.5f;
+ Vector h = aabb.max - center;
+
+ const Vector t[3] { b-a, c-a, c-b };
+
+ Vector ac = a - center;
+
+ Vector n = t[0].cross(t[1]);
+ Scalar s = n.dot(ac);
+ Scalar r = std::abs(h.dot(n.cwiseAbs()));
+ if (abs(s) >= r)
+ return false;
+
+ const Vector at[3] = { t[0].cwiseAbs(), t[1].cwiseAbs(), t[2].cwiseAbs() };
+
+ Vector bc = b - center;
+ Vector cc = c - center;
+
+ // SAT test all cross-axes.
+ // The following is a fully unrolled loop of this code, stored here for reference:
+ /*
+ Scalar d1, d2, a1, a2;
+ const Vector e[3] = { DIR_VEC(1, 0, 0), DIR_VEC(0, 1, 0), DIR_VEC(0, 0, 1) };
+ for(int i = 0; i < 3; ++i)
+ for(int j = 0; j < 3; ++j)
+ {
+ Vector axis = Cross(e[i], t[j]);
+ ProjectToAxis(axis, d1, d2);
+ aabb.ProjectToAxis(axis, a1, a2);
+ if (d2 <= a1 || d1 >= a2) return false;
+ }
+ */
+
+ // eX t[0]
+ Scalar d1 = t[0].y() * ac.z() - t[0].z() * ac.y();
+ Scalar d2 = t[0].y() * cc.z() - t[0].z() * cc.y();
+ Scalar tc = (d1 + d2) * 0.5f;
+ r = std::abs(h.y() * at[0].z() + h.z() * at[0].y());
+ if (r + std::abs(tc - d1) < std::abs(tc))
+ return false;
+
+ // eX t[1]
+ d1 = t[1].y() * ac.z() - t[1].z() * ac.y();
+ d2 = t[1].y() * bc.z() - t[1].z() * bc.y();
+ tc = (d1 + d2) * 0.5f;
+ r = std::abs(h.y() * at[1].z() + h.z() * at[1].y());
+ if (r + std::abs(tc - d1) < std::abs(tc))
+ return false;
+
+ // eX t[2]
+ d1 = t[2].y() * ac.z() - t[2].z() * ac.y();
+ d2 = t[2].y() * bc.z() - t[2].z() * bc.y();
+ tc = (d1 + d2) * 0.5f;
+ r = std::abs(h.y() * at[2].z() + h.z() * at[2].y());
+ if (r + std::abs(tc - d1) < std::abs(tc))
+ return false;
+
+ // eY t[0]
+ d1 = t[0].z() * ac.x() - t[0].x() * ac.z();
+ d2 = t[0].z() * cc.x() - t[0].x() * cc.z();
+ tc = (d1 + d2) * 0.5f;
+ r = std::abs(h.x() * at[0].z() + h.z() * at[0].x());
+ if (r + std::abs(tc - d1) < std::abs(tc))
+ return false;
+
+ // eY t[1]
+ d1 = t[1].z() * ac.x() - t[1].x() * ac.z();
+ d2 = t[1].z() * bc.x() - t[1].x() * bc.z();
+ tc = (d1 + d2) * 0.5f;
+ r = std::abs(h.x() * at[1].z() + h.z() * at[1].x());
+ if (r + std::abs(tc - d1) < std::abs(tc))
+ return false;
+
+ // eY t[2]
+ d1 = t[2].z() * ac.x() - t[2].x() * ac.z();
+ d2 = t[2].z() * bc.x() - t[2].x() * bc.z();
+ tc = (d1 + d2) * 0.5f;
+ r = std::abs(h.x() * at[2].z() + h.z() * at[2].x());
+ if (r + std::abs(tc - d1) < std::abs(tc))
+ return false;
+
+ // eZ t[0]
+ d1 = t[0].x() * ac.y() - t[0].y() * ac.x();
+ d2 = t[0].x() * cc.y() - t[0].y() * cc.x();
+ tc = (d1 + d2) * 0.5f;
+ r = std::abs(h.y() * at[0].x() + h.x() * at[0].y());
+ if (r + std::abs(tc - d1) < std::abs(tc))
+ return false;
+
+ // eZ t[1]
+ d1 = t[1].x() * ac.y() - t[1].y() * ac.x();
+ d2 = t[1].x() * bc.y() - t[1].y() * bc.x();
+ tc = (d1 + d2) * 0.5f;
+ r = std::abs(h.y() * at[1].x() + h.x() * at[1].y());
+ if (r + std::abs(tc - d1) < std::abs(tc))
+ return false;
+
+ // eZ t[2]
+ d1 = t[2].x() * ac.y() - t[2].y() * ac.x();
+ d2 = t[2].x() * bc.y() - t[2].y() * bc.x();
+ tc = (d1 + d2) * 0.5f;
+ r = std::abs(h.y() * at[2].x() + h.x() * at[2].y());
+ if (r + std::abs(tc - d1) < std::abs(tc))
+ return false;
+
+ // No separating axis exists, the AABB and triangle intersect.
+ return true;
+}
+
+// Ordering of children cubes.
+static const std::array child_centers {
+ Vec3d(-1, -1, -1), Vec3d( 1, -1, -1), Vec3d(-1, 1, -1), Vec3d( 1, 1, -1),
+ Vec3d(-1, -1, 1), Vec3d( 1, -1, 1), Vec3d(-1, 1, 1), Vec3d( 1, 1, 1)
+};
+
+// Traversal order of octree children cells for three infill directions,
+// so that a single line will be discretized in a strictly monotonous order.
+static constexpr std::array, 3> child_traversal_order {
+ std::array{ 2, 3, 0, 1, 6, 7, 4, 5 },
+ std::array{ 4, 0, 6, 2, 5, 1, 7, 3 },
+ std::array{ 1, 5, 0, 4, 3, 7, 2, 6 },
+};
+
+struct Cube
+{
+ Vec3d center;
+#ifndef NDEBUG
+ Vec3d center_octree;
+#endif // NDEBUG
+ std::array children {}; // initialized to nullptrs
+ Cube(const Vec3d ¢er) : center(center) {}
+};
+
+struct CubeProperties
+{
+ double edge_length; // Lenght of edge of a cube
+ double height; // Height of rotated cube (standing on the corner)
+ double diagonal_length; // Length of diagonal of a cube a face
+ double line_z_distance; // Defines maximal distance from a center of a cube on Z axis on which lines will be created
+ double line_xy_distance;// Defines maximal distance from a center of a cube on X and Y axis on which lines will be created
+};
+
+struct Octree
+{
+ // Octree will allocate its Cubes from the pool. The pool only supports deletion of the complete pool,
+ // perfect for building up our octree.
+ boost::object_pool pool;
+ Cube* root_cube { nullptr };
+ Vec3d origin;
+ std::vector cubes_properties;
+
+ Octree(const Vec3d &origin, const std::vector &cubes_properties)
+ : root_cube(pool.construct(origin)), origin(origin), cubes_properties(cubes_properties) {}
+
+ void insert_triangle(const Vec3d &a, const Vec3d &b, const Vec3d &c, Cube *current_cube, const BoundingBoxf3 ¤t_bbox, int depth);
+};
+
+void OctreeDeleter::operator()(Octree *p) {
+ delete p;
+}
+
+std::pair adaptive_fill_line_spacing(const PrintObject &print_object)
+{
+ // Output, spacing for icAdaptiveCubic and icSupportCubic
+ double adaptive_line_spacing = 0.;
+ double support_line_spacing = 0.;
+
+ enum class Tristate {
+ Yes,
+ No,
+ Maybe
+ };
+ struct RegionFillData {
+ Tristate has_adaptive_infill;
+ Tristate has_support_infill;
+ double density;
+ double extrusion_width;
+ };
+ std::vector region_fill_data;
+ region_fill_data.reserve(print_object.print()->regions().size());
+ bool build_octree = false;
+ for (const PrintRegion *region : print_object.print()->regions()) {
+ const PrintRegionConfig &config = region->config();
+ bool nonempty = config.fill_density > 0;
+ bool has_adaptive_infill = nonempty && config.fill_pattern == ipAdaptiveCubic;
+ bool has_support_infill = nonempty && config.fill_pattern == ipSupportCubic;
+ region_fill_data.push_back(RegionFillData({
+ has_adaptive_infill ? Tristate::Maybe : Tristate::No,
+ has_support_infill ? Tristate::Maybe : Tristate::No,
+ config.fill_density,
+ config.infill_extrusion_width
+ }));
+ build_octree |= has_adaptive_infill || has_support_infill;
+ }
+
+ if (build_octree) {
+ // Compute the average of above parameters over all layers
+ for (const Layer *layer : print_object.layers())
+ for (size_t region_id = 0; region_id < layer->regions().size(); ++ region_id) {
+ RegionFillData &rd = region_fill_data[region_id];
+ if (rd.has_adaptive_infill == Tristate::Maybe && ! layer->regions()[region_id]->fill_surfaces.empty())
+ rd.has_adaptive_infill = Tristate::Yes;
+ if (rd.has_support_infill == Tristate::Maybe && ! layer->regions()[region_id]->fill_surfaces.empty())
+ rd.has_support_infill = Tristate::Yes;
+ }
+
+ double adaptive_fill_density = 0.;
+ double adaptive_infill_extrusion_width = 0.;
+ int adaptive_cnt = 0;
+ double support_fill_density = 0.;
+ double support_infill_extrusion_width = 0.;
+ int support_cnt = 0;
+
+ for (const RegionFillData &rd : region_fill_data) {
+ if (rd.has_adaptive_infill == Tristate::Yes) {
+ adaptive_fill_density += rd.density;
+ adaptive_infill_extrusion_width += rd.extrusion_width;
+ ++ adaptive_cnt;
+ } else if (rd.has_support_infill == Tristate::Yes) {
+ support_fill_density += rd.density;
+ support_infill_extrusion_width += rd.extrusion_width;
+ ++ support_cnt;
+ }
+ }
+
+ auto to_line_spacing = [](int cnt, double density, double extrusion_width) {
+ if (cnt) {
+ density /= double(cnt);
+ extrusion_width /= double(cnt);
+ return extrusion_width / ((density / 100.0f) * 0.333333333f);
+ } else
+ return 0.;
+ };
+ adaptive_line_spacing = to_line_spacing(adaptive_cnt, adaptive_fill_density, adaptive_infill_extrusion_width);
+ support_line_spacing = to_line_spacing(support_cnt, support_fill_density, support_infill_extrusion_width);
+ }
+
+ return std::make_pair(adaptive_line_spacing, support_line_spacing);
+}
+
+// Context used by generate_infill_lines() when recursively traversing an octree in a DDA fashion
+// (Digital Differential Analyzer).
+struct FillContext
+{
+ // The angles have to agree with child_traversal_order.
+ static constexpr double direction_angles[3] {
+ 0.,
+ (2.0 * M_PI) / 3.0,
+ -(2.0 * M_PI) / 3.0
+ };
+
+ FillContext(const Octree &octree, double z_position, int direction_idx) :
+ origin_world(octree.origin),
+ cubes_properties(octree.cubes_properties),
+ z_position(z_position),
+ traversal_order(child_traversal_order[direction_idx]),
+ cos_a(cos(direction_angles[direction_idx])),
+ sin_a(sin(direction_angles[direction_idx]))
+ {
+ static constexpr auto unused = std::numeric_limits::max();
+ temp_lines.assign((1 << octree.cubes_properties.size()) - 1, Line(Point(unused, unused), Point(unused, unused)));
+ }
+
+ // Rotate the point, uses the same convention as Point::rotate().
+ Vec2d rotate(const Vec2d& v) { return Vec2d(this->cos_a * v.x() - this->sin_a * v.y(), this->sin_a * v.x() + this->cos_a * v.y()); }
+
+ // Center of the root cube in the Octree coordinate system.
+ const Vec3d origin_world;
+ const std::vector &cubes_properties;
+ // Top of the current layer.
+ const double z_position;
+ // Order of traversal for this line direction.
+ const std::array traversal_order;
+ // Rotation of the generated line for this line direction.
+ const double cos_a;
+ const double sin_a;
+
+ // Linearized tree spanning a single Octree wall, used to connect lines spanning
+ // neighboring Octree cells. Unused lines have the Line::a::x set to infinity.
+ std::vector temp_lines;
+ // Final output
+ std::vector output_lines;
+};
+
+static constexpr double octree_rot[3] = { 5.0 * M_PI / 4.0, Geometry::deg2rad(215.264), M_PI / 6.0 };
+
+Eigen::Quaterniond transform_to_world()
+{
+ return Eigen::AngleAxisd(octree_rot[2], Vec3d::UnitZ()) * Eigen::AngleAxisd(octree_rot[1], Vec3d::UnitY()) * Eigen::AngleAxisd(octree_rot[0], Vec3d::UnitX());
+}
+
+Eigen::Quaterniond transform_to_octree()
+{
+ return Eigen::AngleAxisd(- octree_rot[0], Vec3d::UnitX()) * Eigen::AngleAxisd(- octree_rot[1], Vec3d::UnitY()) * Eigen::AngleAxisd(- octree_rot[2], Vec3d::UnitZ());
+}
+
+#ifndef NDEBUG
+// Verify that the traversal order of the octree children matches the line direction,
+// therefore the infill line may get extended with O(1) time & space complexity.
+static bool verify_traversal_order(
+ FillContext &context,
+ const Cube *cube,
+ int depth,
+ const Vec2d &line_from,
+ const Vec2d &line_to)
+{
+ std::array c;
+ Eigen::Quaterniond to_world = transform_to_world();
+ for (int i = 0; i < 8; ++i) {
+ int j = context.traversal_order[i];
+ Vec3d cntr = to_world * (cube->center_octree + (child_centers[j] * (context.cubes_properties[depth].edge_length / 4.)));
+ assert(!cube->children[j] || cube->children[j]->center.isApprox(cntr));
+ c[i] = cntr;
+ }
+ std::array dirs = {
+ c[1] - c[0], c[2] - c[0], c[3] - c[1], c[3] - c[2], c[3] - c[0],
+ c[5] - c[4], c[6] - c[4], c[7] - c[5], c[7] - c[6], c[7] - c[4]
+ };
+ assert(std::abs(dirs[4].z()) < 0.001);
+ assert(std::abs(dirs[9].z()) < 0.001);
+ assert(dirs[0].isApprox(dirs[3]));
+ assert(dirs[1].isApprox(dirs[2]));
+ assert(dirs[5].isApprox(dirs[8]));
+ assert(dirs[6].isApprox(dirs[7]));
+ Vec3d line_dir = Vec3d(line_to.x() - line_from.x(), line_to.y() - line_from.y(), 0.).normalized();
+ for (auto& dir : dirs) {
+ double d = dir.normalized().dot(line_dir);
+ assert(d > 0.7);
+ }
+ return true;
+}
+#endif // NDEBUG
+
+static void generate_infill_lines_recursive(
+ FillContext &context,
+ const Cube *cube,
+ // Address of this wall in the octree, used to address context.temp_lines.
+ int address,
+ int depth)
+{
+ assert(cube != nullptr);
+
+ const std::vector &cubes_properties = context.cubes_properties;
+ const double z_diff = context.z_position - cube->center.z();
+ const double z_diff_abs = std::abs(z_diff);
+
+ if (z_diff_abs > cubes_properties[depth].height / 2.)
+ return;
+
+ if (z_diff_abs < cubes_properties[depth].line_z_distance) {
+ // Discretize a single wall splitting the cube into two.
+ const double zdist = cubes_properties[depth].line_z_distance;
+ Vec2d from(
+ 0.5 * cubes_properties[depth].diagonal_length * (zdist - z_diff_abs) / zdist,
+ cubes_properties[depth].line_xy_distance - (zdist + z_diff) / sqrt(2.));
+ Vec2d to(-from.x(), from.y());
+ from = context.rotate(from);
+ to = context.rotate(to);
+ // Relative to cube center
+ Vec2d offset(cube->center.x() - context.origin_world.x(), cube->center.y() - context.origin_world.y());
+ from += offset;
+ to += offset;
+ // Verify that the traversal order of the octree children matches the line direction,
+ // therefore the infill line may get extended with O(1) time & space complexity.
+ assert(verify_traversal_order(context, cube, depth, from, to));
+ // Either extend an existing line or start a new one.
+ Line &last_line = context.temp_lines[address];
+ Line new_line(Point::new_scale(from), Point::new_scale(to));
+ if (last_line.a.x() == std::numeric_limits::max()) {
+ last_line.a = new_line.a;
+ } else if ((new_line.a - last_line.b).cwiseAbs().maxCoeff() > 300) { // SCALED_EPSILON is 100 and it is not enough) {
+ context.output_lines.emplace_back(last_line);
+ last_line.a = new_line.a;
+ }
+ last_line.b = new_line.b;
+ }
+
+ // left child index
+ address = address * 2 + 1;
+ -- depth;
+ size_t i = 0;
+ for (const int child_idx : context.traversal_order) {
+ const Cube *child = cube->children[child_idx];
+ if (child != nullptr)
+ generate_infill_lines_recursive(context, child, address, depth);
+ if (++ i == 4)
+ // right child index
+ ++ address;
+ }
+}
+
+#if 0
+// Collect the line segments.
+static Polylines chain_lines(const std::vector &lines, const double point_distance_epsilon)
+{
+ // Create line end point lookup.
+ struct LineEnd {
+ LineEnd(Line *line, bool start) : line(line), start(start) {}
+ Line *line;
+ // Is it the start or end point?
+ bool start;
+ const Point& point() const { return start ? line->a : line->b; }
+ const Point& other_point() const { return start ? line->b : line->a; }
+ LineEnd other_end() const { return LineEnd(line, ! start); }
+ bool operator==(const LineEnd &rhs) const { return this->line == rhs.line && this->start == rhs.start; }
+ };
+ struct LineEndAccessor {
+ const Point* operator()(const LineEnd &pt) const { return &pt.point(); }
+ };
+ typedef ClosestPointInRadiusLookup ClosestPointLookupType;
+ ClosestPointLookupType closest_end_point_lookup(point_distance_epsilon);
+ for (const Line &line : lines) {
+ closest_end_point_lookup.insert(LineEnd(&line, true));
+ closest_end_point_lookup.insert(LineEnd(&line, false));
+ }
+
+ // Chain the lines.
+ std::vector line_consumed(lines.size(), false);
+ static const double point_distance_epsilon2 = point_distance_epsilon * point_distance_epsilon;
+ Polylines out;
+ for (const Line &seed : lines)
+ if (! line_consumed[&seed - lines.data()]) {
+ line_consumed[&seed - lines.data()] = true;
+ closest_end_point_lookup.erase(LineEnd(&seed, false));
+ closest_end_point_lookup.erase(LineEnd(&seed, true));
+ Polyline pl { seed.a, seed.b };
+ for (size_t round = 0; round < 2; ++ round) {
+ for (;;) {
+ auto [line_end, dist2] = closest_end_point_lookup.find(pl.last_point());
+ if (line_end == nullptr || dist2 >= point_distance_epsilon2)
+ // Cannot extent in this direction.
+ break;
+ // Average the last point.
+ pl.points.back() = 0.5 * (pl.points.back() + line_end->point());
+ // and extend with the new line segment.
+ pl.points.emplace_back(line_end->other_point());
+ closest_end_point_lookup.erase(line_end);
+ closest_end_point_lookup.erase(line_end->other_end());
+ line_consumed[line_end->line - lines.data()] = true;
+ }
+ // reverse and try the oter direction.
+ pl.reverse();
+ }
+ out.emplace_back(std::move(pl));
+ }
+ return out;
+}
+#endif
+
+#ifndef NDEBUG
+// #define ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT
+#endif
+
+#ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT
+static void export_infill_lines_to_svg(const ExPolygon &expoly, const Polylines &polylines, const std::string &path)
+{
+ BoundingBox bbox = get_extents(expoly);
+ bbox.offset(scale_(3.));
+
+ ::Slic3r::SVG svg(path, bbox);
+ svg.draw(expoly);
+ svg.draw_outline(expoly, "green");
+ svg.draw(polylines, "red");
+ static constexpr double trim_length = scale_(0.4);
+ for (Polyline polyline : polylines) {
+ Vec2d a = polyline.points.front().cast();
+ Vec2d d = polyline.points.back().cast();
+ if (polyline.size() == 2) {
+ Vec2d v = d - a;
+ double l = v.norm();
+ if (l > 2. * trim_length) {
+ a += v * trim_length / l;
+ d -= v * trim_length / l;
+ polyline.points.front() = a.cast();
+ polyline.points.back() = d.cast();
+ } else
+ polyline.points.clear();
+ } else if (polyline.size() > 2) {
+ Vec2d b = polyline.points[1].cast();
+ Vec2d c = polyline.points[polyline.points.size() - 2].cast();
+ Vec2d v = b - a;
+ double l = v.norm();
+ if (l > trim_length) {
+ a += v * trim_length / l;
+ polyline.points.front() = a.cast();
+ } else
+ polyline.points.erase(polyline.points.begin());
+ v = d - c;
+ l = v.norm();
+ if (l > trim_length)
+ polyline.points.back() = (d - v * trim_length / l).cast();
+ else
+ polyline.points.pop_back();
+ }
+ svg.draw(polyline, "black");
+ }
+}
+#endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */
+
+void Filler::_fill_surface_single(
+ const FillParams & params,
+ unsigned int thickness_layers,
+ const std::pair &direction,
+ ExPolygon &expolygon,
+ Polylines &polylines_out)
+{
+ assert (this->adapt_fill_octree);
+
+ Polylines all_polylines;
+ {
+ // 3 contexts for three directions of infill lines
+ std::array contexts {
+ FillContext { *adapt_fill_octree, this->z, 0 },
+ FillContext { *adapt_fill_octree, this->z, 1 },
+ FillContext { *adapt_fill_octree, this->z, 2 }
+ };
+ // Generate the infill lines along the octree cells, merge touching lines of the same direction.
+ size_t num_lines = 0;
+ for (auto &context : contexts) {
+ generate_infill_lines_recursive(context, adapt_fill_octree->root_cube, 0, int(adapt_fill_octree->cubes_properties.size()) - 1);
+ num_lines += context.output_lines.size() + context.temp_lines.size();
+ }
+ // Collect the lines.
+ std::vector lines;
+ lines.reserve(num_lines);
+ for (auto &context : contexts) {
+ append(lines, context.output_lines);
+ for (const Line &line : context.temp_lines)
+ if (line.a.x() != std::numeric_limits::max())
+ lines.emplace_back(line);
+ }
+ // Convert lines to polylines.
+ //FIXME chain the lines
+ all_polylines.reserve(lines.size());
+ std::transform(lines.begin(), lines.end(), std::back_inserter(all_polylines), [](const Line& l) { return Polyline{ l.a, l.b }; });
+ }
+
+ // Crop all polylines
+ all_polylines = intersection_pl(std::move(all_polylines), to_polygons(expolygon));
+
+#ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT
+ {
+ static int iRun = 0;
+ export_infill_lines_to_svg(expolygon, all_polylines, debug_out_path("FillAdaptive-initial-%d.svg", iRun++));
+ }
+#endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */
+
+ if (params.dont_connect)
+ append(polylines_out, std::move(all_polylines));
+ else
+ connect_infill(chain_polylines(std::move(all_polylines)), expolygon, polylines_out, this->spacing, params);
+
+#ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT
+ {
+ static int iRun = 0;
+ export_infill_lines_to_svg(expolygon, polylines_out, debug_out_path("FillAdaptive-final-%d.svg", iRun ++));
+ }
+#endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */
+}
+
+static double bbox_max_radius(const BoundingBoxf3 &bbox, const Vec3d ¢er)
+{
+ const auto p = (bbox.min - center);
+ const auto s = bbox.size();
+ double r2max = 0.;
+ for (int i = 0; i < 8; ++ i)
+ r2max = std::max(r2max, (p + Vec3d(s.x() * double(i & 1), s.y() * double(i & 2), s.z() * double(i & 4))).squaredNorm());
+ return sqrt(r2max);
+}
+
+static std::vector make_cubes_properties(double max_cube_edge_length, double line_spacing)
+{
+ max_cube_edge_length += EPSILON;
+
+ std::vector cubes_properties;
+ for (double edge_length = line_spacing * 2.;; edge_length *= 2.)
+ {
+ CubeProperties props{};
+ props.edge_length = edge_length;
+ props.height = edge_length * sqrt(3);
+ props.diagonal_length = edge_length * sqrt(2);
+ props.line_z_distance = edge_length / sqrt(3);
+ props.line_xy_distance = edge_length / sqrt(6);
+ cubes_properties.emplace_back(props);
+ if (edge_length > max_cube_edge_length)
+ break;
+ }
+ return cubes_properties;
+}
+
+static inline bool is_overhang_triangle(const Vec3d &a, const Vec3d &b, const Vec3d &c, const Vec3d &up)
+{
+ // Calculate triangle normal.
+ auto n = (b - a).cross(c - b);
+ return n.dot(up) > 0.707 * n.norm();
+}
+
+static void transform_center(Cube *current_cube, const Eigen::Matrix3d &rot)
+{
+#ifndef NDEBUG
+ current_cube->center_octree = current_cube->center;
+#endif // NDEBUG
+ current_cube->center = rot * current_cube->center;
+ for (auto *child : current_cube->children)
+ if (child)
+ transform_center(child, rot);
+}
+
+OctreePtr build_octree(const indexed_triangle_set &triangle_mesh, coordf_t line_spacing, bool support_overhangs_only)
+{
+ assert(line_spacing > 0);
+ assert(! std::isnan(line_spacing));
+
+ BoundingBox3Base bbox(triangle_mesh.vertices);
+ Vec3d cube_center = bbox.center().cast();
+ std::vector cubes_properties = make_cubes_properties(double(bbox.size().maxCoeff()), line_spacing);
+ auto octree = OctreePtr(new Octree(cube_center, cubes_properties));
+
+ if (cubes_properties.size() > 1) {
+ auto up_vector = support_overhangs_only ? Vec3d(transform_to_octree() * Vec3d(0., 0., 1.)) : Vec3d();
+ for (auto &tri : triangle_mesh.indices) {
+ auto a = triangle_mesh.vertices[tri[0]].cast();
+ auto b = triangle_mesh.vertices[tri[1]].cast();
+ auto c = triangle_mesh.vertices[tri[2]].cast();
+ if (support_overhangs_only && ! is_overhang_triangle(a, b, c, up_vector))
+ continue;
+ double edge_length_half = 0.5 * cubes_properties.back().edge_length;
+ Vec3d diag_half(edge_length_half, edge_length_half, edge_length_half);
+ octree->insert_triangle(
+ a, b, c,
+ octree->root_cube,
+ BoundingBoxf3(octree->root_cube->center - diag_half, octree->root_cube->center + diag_half),
+ int(cubes_properties.size()) - 1);
+ }
+ {
+ // Transform the octree to world coordinates to reduce computation when extracting infill lines.
+ auto rot = transform_to_world().toRotationMatrix();
+ transform_center(octree->root_cube, rot);
+ octree->origin = rot * octree->origin;
+ }
+ }
+
+ return octree;
+}
+
+void Octree::insert_triangle(const Vec3d &a, const Vec3d &b, const Vec3d &c, Cube *current_cube, const BoundingBoxf3 ¤t_bbox, int depth)
+{
+ assert(current_cube);
+ assert(depth > 0);
+
+ for (size_t i = 0; i < 8; ++ i) {
+ const Vec3d &child_center = child_centers[i];
+ // Calculate a slightly expanded bounding box of a child cube to cope with triangles touching a cube wall and other numeric errors.
+ // We will rather densify the octree a bit more than necessary instead of missing a triangle.
+ BoundingBoxf3 bbox;
+ for (int k = 0; k < 3; ++ k) {
+ if (child_center[k] == -1.) {
+ bbox.min[k] = current_bbox.min[k];
+ bbox.max[k] = current_cube->center[k] + EPSILON;
+ } else {
+ bbox.min[k] = current_cube->center[k] - EPSILON;
+ bbox.max[k] = current_bbox.max[k];
+ }
+ }
+ if (triangle_AABB_intersects(a, b, c, bbox)) {
+ if (! current_cube->children[i])
+ current_cube->children[i] = this->pool.construct(current_cube->center + (child_center * (this->cubes_properties[depth].edge_length / 4)));
+ if (depth > 1)
+ this->insert_triangle(a, b, c, current_cube->children[i], bbox, depth - 1);
+ }
+ }
+}
+
+} // namespace FillAdaptive
+} // namespace Slic3r
diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp
new file mode 100644
index 000000000..aca8d1d7b
--- /dev/null
+++ b/src/libslic3r/Fill/FillAdaptive.hpp
@@ -0,0 +1,72 @@
+// Adaptive cubic infill was inspired by the work of @mboerwinkle
+// as implemented for Cura.
+// https://github.com/Ultimaker/CuraEngine/issues/381
+// https://github.com/Ultimaker/CuraEngine/pull/401
+//
+// Our implementation is more accurate (discretizes a bit less cubes than Cura's)
+// by splitting only such cubes which contain a triangle.
+// Our line extraction is time optimal instead of O(n^2) when connecting extracted lines,
+// and we also implemented adaptivity for supporting internal overhangs only.
+
+#ifndef slic3r_FillAdaptive_hpp_
+#define slic3r_FillAdaptive_hpp_
+
+#include "FillBase.hpp"
+
+struct indexed_triangle_set;
+
+namespace Slic3r {
+
+class PrintObject;
+
+namespace FillAdaptive
+{
+
+struct Octree;
+// To keep the definition of Octree opaque, we have to define a custom deleter.
+struct OctreeDeleter { void operator()(Octree *p); };
+using OctreePtr = std::unique_ptr;
+
+// Calculate line spacing for
+// 1) adaptive cubic infill
+// 2) adaptive internal support cubic infill
+// Returns zero for a particular infill type if no such infill is to be generated.
+std::pair adaptive_fill_line_spacing(const PrintObject &print_object);
+
+// Rotation of the octree to stand on one of its corners.
+Eigen::Quaterniond transform_to_world();
+// Inverse roation of the above.
+Eigen::Quaterniond transform_to_octree();
+
+FillAdaptive::OctreePtr build_octree(
+ // Mesh is rotated to the coordinate system of the octree.
+ const indexed_triangle_set &triangle_mesh,
+ coordf_t line_spacing,
+ // If true, octree is densified below internal overhangs only.
+ bool support_overhangs_only);
+
+//
+// Some of the algorithms used by class FillAdaptive were inspired by
+// Cura Engine's class SubDivCube
+// https://github.com/Ultimaker/CuraEngine/blob/master/src/infill/SubDivCube.h
+//
+class Filler : public Slic3r::Fill
+{
+public:
+ virtual ~Filler() {}
+
+protected:
+ virtual Fill* clone() const { return new Filler(*this); };
+ virtual void _fill_surface_single(
+ const FillParams ¶ms,
+ unsigned int thickness_layers,
+ const std::pair &direction,
+ ExPolygon &expolygon,
+ Polylines &polylines_out);
+ virtual bool no_sort() const { return true; }
+};
+
+}; // namespace FillAdaptive
+} // namespace Slic3r
+
+#endif // slic3r_FillAdaptive_hpp_
diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp
index c760218c0..fc5f548a3 100644
--- a/src/libslic3r/Fill/FillBase.cpp
+++ b/src/libslic3r/Fill/FillBase.cpp
@@ -16,6 +16,7 @@
#include "FillRectilinear.hpp"
#include "FillRectilinear2.hpp"
#include "FillRectilinear3.hpp"
+#include "FillAdaptive.hpp"
namespace Slic3r {
@@ -37,7 +38,9 @@ Fill* Fill::new_from_type(const InfillPattern type)
case ipArchimedeanChords: return new FillArchimedeanChords();
case ipHilbertCurve: return new FillHilbertCurve();
case ipOctagramSpiral: return new FillOctagramSpiral();
- default: throw std::invalid_argument("unknown type");
+ case ipAdaptiveCubic: return new FillAdaptive::Filler();
+ case ipSupportCubic: return new FillAdaptive::Filler();
+ default: throw Slic3r::InvalidArgument("unknown type");
}
}
@@ -844,8 +847,9 @@ void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_
boundary.assign(boundary_src.holes.size() + 1, Points());
boundary_data.assign(boundary_src.holes.size() + 1, std::vector