Merge branch 'master' into lm_seam_painter_backend

This commit is contained in:
Lukas Matena 2020-09-18 13:44:45 +02:00
commit 6db2d3a0b2
175 changed files with 7346 additions and 2847 deletions

View file

@ -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 "$<TARGET_FILE_DIR:PrusaSlicer>"
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 "$<TARGET_FILE_DIR:PrusaSlicer>"
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)

View file

@ -22,6 +22,7 @@
#include <cstring>
#include <iostream>
#include <math.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem.hpp>
#include <boost/nowide/args.hpp>
#include <boost/nowide/cenv.hpp>
@ -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<std::string> &load_configs = m_config.option<ConfigOptionStrings>("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;

View file

@ -255,18 +255,24 @@ extern void its_transform(indexed_triangle_set &its, T *trafo3x4)
}
template<typename T>
inline void its_transform(indexed_triangle_set &its, const Eigen::Transform<T, 3, Eigen::Affine, Eigen::DontAlign>& t)
inline void its_transform(indexed_triangle_set &its, const Eigen::Transform<T, 3, Eigen::Affine, Eigen::DontAlign>& t, bool fix_left_handed = false)
{
//const Eigen::Matrix<double, 3, 3, Eigen::DontAlign> r = t.matrix().template block<3, 3>(0, 0);
for (stl_vertex &v : its.vertices)
v = (t * v.template cast<T>()).template cast<float>().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<typename T>
inline void its_transform(indexed_triangle_set &its, const Eigen::Matrix<T, 3, 3, Eigen::DontAlign>& m)
inline void its_transform(indexed_triangle_set &its, const Eigen::Matrix<T, 3, 3, Eigen::DontAlign>& m, bool fix_left_handed = false)
{
for (stl_vertex &v : its.vertices)
for (stl_vertex &v : its.vertices)
v = (m * v.template cast<T>()).template cast<float>().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);

View file

@ -283,7 +283,7 @@ namespace detail {
template<typename V, typename W>
std::enable_if_t<std::is_same<typename V::Scalar, double>::value && std::is_same<typename W::Scalar, double>::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<double*>(origin.data()), const_cast<double*>(dir.data()),
const_cast<double*>(v0.data()), const_cast<double*>(v1.data()), const_cast<double*>(v2.data()),
&t, &u, &v);
@ -291,7 +291,7 @@ namespace detail {
template<typename V, typename W>
std::enable_if_t<std::is_same<typename V::Scalar, double>::value && !std::is_same<typename W::Scalar, double>::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<double, 3, 1>;
Vector w0 = v0.template cast<double>();
Vector w1 = v1.template cast<double>();
@ -302,7 +302,7 @@ namespace detail {
template<typename V, typename W>
std::enable_if_t<! std::is_same<typename V::Scalar, double>::value && std::is_same<typename W::Scalar, double>::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<double, 3, 1>;
Vector o = origin.template cast<double>();
Vector d = dir.template cast<double>();
@ -311,7 +311,7 @@ namespace detail {
template<typename V, typename W>
std::enable_if_t<! std::is_same<typename V::Scalar, double>::value && ! std::is_same<typename W::Scalar, double>::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<double, 3, 1>;
Vector o = origin.template cast<double>();
Vector d = dir.template cast<double>();
@ -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<Scalar>::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<typename VertexType, typename IndexedFaceType, typename TreeType, typename VectorType>
inline bool is_any_triangle_in_radius(
// Indexed triangle set - 3D vertices.
const std::vector<VertexType> &vertices,
// Indexed triangle set - triangular faces, references to vertices.
const std::vector<IndexedFaceType> &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<VertexType, IndexedFaceType, TreeType, VectorType>
{ 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

View file

@ -1,6 +1,7 @@
#include "libslic3r/libslic3r.h"
#include "libslic3r/Utils.hpp"
#include "AppConfig.hpp"
#include "Exception.hpp"
#include <utility>
#include <vector>
@ -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();

View file

@ -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;
};

View file

@ -75,6 +75,7 @@ BoundingBoxBase<PointClass>::merge(const PointClass &point)
}
}
template void BoundingBoxBase<Point>::merge(const Point &point);
template void BoundingBoxBase<Vec2f>::merge(const Vec2f &point);
template void BoundingBoxBase<Vec2d>::merge(const Vec2d &point);
template <class PointClass> void
@ -101,6 +102,7 @@ BoundingBoxBase<PointClass>::merge(const BoundingBoxBase<PointClass> &bb)
}
}
template void BoundingBoxBase<Point>::merge(const BoundingBoxBase<Point> &bb);
template void BoundingBoxBase<Vec2f>::merge(const BoundingBoxBase<Vec2f> &bb);
template void BoundingBoxBase<Vec2d>::merge(const BoundingBoxBase<Vec2d> &bb);
template <class PointClass> void
@ -115,6 +117,7 @@ BoundingBox3Base<PointClass>::merge(const PointClass &point)
this->defined = true;
}
}
template void BoundingBox3Base<Vec3f>::merge(const Vec3f &point);
template void BoundingBox3Base<Vec3d>::merge(const Vec3d &point);
template <class PointClass> void
@ -147,6 +150,7 @@ BoundingBoxBase<PointClass>::size() const
return PointClass(this->max(0) - this->min(0), this->max(1) - this->min(1));
}
template Point BoundingBoxBase<Point>::size() const;
template Vec2f BoundingBoxBase<Vec2f>::size() const;
template Vec2d BoundingBoxBase<Vec2d>::size() const;
template <class PointClass> PointClass
@ -154,6 +158,7 @@ BoundingBox3Base<PointClass>::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<Vec3f>::size() const;
template Vec3d BoundingBox3Base<Vec3d>::size() const;
template <class PointClass> double BoundingBoxBase<PointClass>::radius() const
@ -200,6 +205,7 @@ BoundingBoxBase<PointClass>::center() const
return (this->min + this->max) / 2;
}
template Point BoundingBoxBase<Point>::center() const;
template Vec2f BoundingBoxBase<Vec2f>::center() const;
template Vec2d BoundingBoxBase<Vec2d>::center() const;
template <class PointClass> PointClass
@ -207,6 +213,7 @@ BoundingBox3Base<PointClass>::center() const
{
return (this->min + this->max) / 2;
}
template Vec3f BoundingBox3Base<Vec3f>::center() const;
template Vec3d BoundingBox3Base<Vec3d>::center() const;
template <class PointClass> coordf_t
@ -215,6 +222,7 @@ BoundingBox3Base<PointClass>::max_size() const
PointClass s = size();
return std::max(s(0), std::max(s(1), s(2)));
}
template coordf_t BoundingBox3Base<Vec3f>::max_size() const;
template coordf_t BoundingBox3Base<Vec3d>::max_size() const;
// Align a coordinate to a grid. The coordinate may be negative,

View file

@ -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<PointClass>& 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<PointClass>::const_iterator it = points.begin();
this->min = *it;
@ -65,10 +68,12 @@ public:
BoundingBox3Base(const PointClass &pmin, const PointClass &pmax) :
BoundingBoxBase<PointClass>(pmin, pmax)
{ if (pmin(2) >= pmax(2)) BoundingBoxBase<PointClass>::defined = false; }
BoundingBox3Base(const PointClass &p1, const PointClass &p2, const PointClass &p3) :
BoundingBoxBase<PointClass>(p1, p1) { merge(p2); merge(p3); }
BoundingBox3Base(const std::vector<PointClass>& 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<PointClass>::const_iterator it = points.begin();
this->min = *it;
this->max = *it;
@ -109,24 +114,32 @@ extern template void BoundingBoxBase<Vec3d>::scale(double factor);
extern template void BoundingBoxBase<Point>::offset(coordf_t delta);
extern template void BoundingBoxBase<Vec2d>::offset(coordf_t delta);
extern template void BoundingBoxBase<Point>::merge(const Point &point);
extern template void BoundingBoxBase<Vec2f>::merge(const Vec2f &point);
extern template void BoundingBoxBase<Vec2d>::merge(const Vec2d &point);
extern template void BoundingBoxBase<Point>::merge(const Points &points);
extern template void BoundingBoxBase<Vec2d>::merge(const Pointfs &points);
extern template void BoundingBoxBase<Point>::merge(const BoundingBoxBase<Point> &bb);
extern template void BoundingBoxBase<Vec2f>::merge(const BoundingBoxBase<Vec2f> &bb);
extern template void BoundingBoxBase<Vec2d>::merge(const BoundingBoxBase<Vec2d> &bb);
extern template Point BoundingBoxBase<Point>::size() const;
extern template Vec2f BoundingBoxBase<Vec2f>::size() const;
extern template Vec2d BoundingBoxBase<Vec2d>::size() const;
extern template double BoundingBoxBase<Point>::radius() const;
extern template double BoundingBoxBase<Vec2d>::radius() const;
extern template Point BoundingBoxBase<Point>::center() const;
extern template Vec2f BoundingBoxBase<Vec2f>::center() const;
extern template Vec2d BoundingBoxBase<Vec2d>::center() const;
extern template void BoundingBox3Base<Vec3f>::merge(const Vec3f &point);
extern template void BoundingBox3Base<Vec3d>::merge(const Vec3d &point);
extern template void BoundingBox3Base<Vec3d>::merge(const Pointf3s &points);
extern template void BoundingBox3Base<Vec3d>::merge(const BoundingBox3Base<Vec3d> &bb);
extern template Vec3f BoundingBox3Base<Vec3f>::size() const;
extern template Vec3d BoundingBox3Base<Vec3d>::size() const;
extern template double BoundingBox3Base<Vec3d>::radius() const;
extern template void BoundingBox3Base<Vec3d>::offset(coordf_t delta);
extern template Vec3f BoundingBox3Base<Vec3f>::center() const;
extern template Vec3d BoundingBox3Base<Vec3d>::center() const;
extern template coordf_t BoundingBox3Base<Vec3f>::max_size() const;
extern template coordf_t BoundingBox3Base<Vec3d>::max_size() const;
class BoundingBox : public BoundingBoxBase<Point>

View file

@ -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

View file

@ -5,7 +5,6 @@
#include <fstream>
#include <iostream>
#include <iomanip>
#include <exception> // std::runtime_error
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/erase.hpp>
@ -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<const ConfigOptionFloatOrPercent*>(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<const ConfigOptionFloatOrPercent*>(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();

View file

@ -13,6 +13,7 @@
#include <vector>
#include "libslic3r.h"
#include "clonable_ptr.hpp"
#include "Exception.hpp"
#include "Point.hpp"
#include <boost/algorithm/string/trim.hpp>
@ -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<std::string> &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<const ConfigOptionSingle<T>*>(rhs));
this->value = static_cast<const ConfigOptionSingle<T>*>(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<const ConfigOptionSingle<T>*>(&rhs));
return this->value == static_cast<const ConfigOptionSingle<T>*>(&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<const ConfigOptionVector<T>*>(rhs));
this->values = static_cast<const ConfigOptionVector<T>*>(rhs)->values;
}
@ -256,12 +257,12 @@ public:
if (opt->type() == this->type()) {
auto other = static_cast<const ConfigOptionVector<T>*>(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<const ConfigOptionSingle<T>*>(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<const ConfigOptionVector<T>*>(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<const ConfigOptionSingle<T>*>(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<const ConfigOptionVector<T>*>(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<const ConfigOptionVector<T>*>(&rhs));
return this->values == static_cast<const ConfigOptionVector<T>*>(&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<const ConfigOptionVector<T>*>(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<const ConfigOptionVector<T>*>(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<const ConfigOptionVector<double>*>(&rhs));
return vectors_equal(this->values, static_cast<const ConfigOptionVector<double>*>(&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<double> &v1, const std::vector<double> &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<const ConfigOptionFloatOrPercent*>(&rhs));
return *this == *static_cast<const ConfigOptionFloatOrPercent*>(&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<const ConfigOptionFloatOrPercent*>(rhs));
*this = *static_cast<const ConfigOptionFloatOrPercent*>(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<T>: Comparing incompatible types");
throw Slic3r::RuntimeError("ConfigOptionEnum<T>: Comparing incompatible types");
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
return this->value == (T)rhs.getInt();
}
void set(const ConfigOption *rhs) override {
if (rhs->type() != this->type())
throw std::runtime_error("ConfigOptionEnum<T>: Assigning an incompatible type");
throw Slic3r::RuntimeError("ConfigOptionEnum<T>: Assigning an incompatible type");
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
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<T>
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<T>
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<const ConfigOptionIntsNullable*>(opt)); break;
case coPercents: archive(*static_cast<const ConfigOptionPercentsNullable*>(opt));break;
case coBools: archive(*static_cast<const ConfigOptionBoolsNullable*>(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<const ConfigOptionBool*>(opt)); break;
case coBools: archive(*static_cast<const ConfigOptionBools*>(opt)); break;
case coEnum: archive(*static_cast<const ConfigOptionEnumGeneric*>(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.

View file

@ -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<TPPLPoly> 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<TPPLPoly>::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
}

View file

@ -333,6 +333,14 @@ extern std::list<TPPLPoly> expoly_to_polypartition_input(const ExPolygons &expp)
extern std::list<TPPLPoly> expoly_to_polypartition_input(const ExPolygon &ex);
extern std::vector<Point> polypartition_output_to_triangles(const std::list<TPPLPoly> &output);
inline double area(const ExPolygons &polys)
{
double s = 0.;
for (auto &p : polys) s += p.area();
return s;
}
} // namespace Slic3r
// start Boost

View file

@ -0,0 +1,28 @@
#ifndef _libslic3r_Exception_h_
#define _libslic3r_Exception_h_
#include <stdexcept>
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_

View file

@ -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.;
}
};

View file

@ -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;

View file

@ -318,7 +318,7 @@ void export_group_fills_to_svg(const char *path, const std::vector<SurfaceFill>
#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;

View file

@ -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 <admesh/stl.h>
#include <cstdlib>
#include <cmath>
// Boost pool: Don't use mutexes to synchronize memory allocation.
#define BOOST_POOL_NO_MT
#include <boost/pool/object_pool.hpp>
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<typename Vector>
bool triangle_AABB_intersects(const Vector &a, const Vector &b, const Vector &c, const BoundingBoxBase<Vector> &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 <cross> 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 <cross> 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 <cross> 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 <cross> 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 <cross> 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 <cross> 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 <cross> 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 <cross> 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 <cross> 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<Vec3d, 8> 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<std::array<int, 8>, 3> child_traversal_order {
std::array<int, 8>{ 2, 3, 0, 1, 6, 7, 4, 5 },
std::array<int, 8>{ 4, 0, 6, 2, 5, 1, 7, 3 },
std::array<int, 8>{ 1, 5, 0, 4, 3, 7, 2, 6 },
};
struct Cube
{
Vec3d center;
#ifndef NDEBUG
Vec3d center_octree;
#endif // NDEBUG
std::array<Cube*, 8> children {}; // initialized to nullptrs
Cube(const Vec3d &center) : 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<Cube> pool;
Cube* root_cube { nullptr };
Vec3d origin;
std::vector<CubeProperties> cubes_properties;
Octree(const Vec3d &origin, const std::vector<CubeProperties> &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 &current_bbox, int depth);
};
void OctreeDeleter::operator()(Octree *p) {
delete p;
}
std::pair<double, double> 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<RegionFillData> 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<coord_t>::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<CubeProperties> &cubes_properties;
// Top of the current layer.
const double z_position;
// Order of traversal for this line direction.
const std::array<int, 8> 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<Line> temp_lines;
// Final output
std::vector<Line> 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<Vec3d, 8> 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<Vec3d, 10> 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<CubeProperties> &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<coord_t>::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<Line> &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<LineEnd, LineEndAccessor> 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<char> 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<double>();
Vec2d d = polyline.points.back().cast<double>();
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<coord_t>();
polyline.points.back() = d.cast<coord_t>();
} else
polyline.points.clear();
} else if (polyline.size() > 2) {
Vec2d b = polyline.points[1].cast<double>();
Vec2d c = polyline.points[polyline.points.size() - 2].cast<double>();
Vec2d v = b - a;
double l = v.norm();
if (l > trim_length) {
a += v * trim_length / l;
polyline.points.front() = a.cast<coord_t>();
} 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<coord_t>();
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<float, Point> &direction,
ExPolygon &expolygon,
Polylines &polylines_out)
{
assert (this->adapt_fill_octree);
Polylines all_polylines;
{
// 3 contexts for three directions of infill lines
std::array<FillContext, 3> 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<Line> 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<coord_t>::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 &center)
{
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<CubeProperties> make_cubes_properties(double max_cube_edge_length, double line_spacing)
{
max_cube_edge_length += EPSILON;
std::vector<CubeProperties> 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<Vec3f> bbox(triangle_mesh.vertices);
Vec3d cube_center = bbox.center().cast<double>();
std::vector<CubeProperties> 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<double>();
auto b = triangle_mesh.vertices[tri[1]].cast<double>();
auto c = triangle_mesh.vertices[tri[2]].cast<double>();
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 &current_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

View file

@ -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<Octree, OctreeDeleter>;
// 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<double, double> 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 &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon &expolygon,
Polylines &polylines_out);
virtual bool no_sort() const { return true; }
};
}; // namespace FillAdaptive
} // namespace Slic3r
#endif // slic3r_FillAdaptive_hpp_

View file

@ -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<ContourPointData>());
// Mapping the infill_ordered end point to a (contour, point) of boundary.
std::vector<std::pair<size_t, size_t>> map_infill_end_point_to_boundary;
map_infill_end_point_to_boundary.assign(infill_ordered.size() * 2, std::pair<size_t, size_t>(std::numeric_limits<size_t>::max(), std::numeric_limits<size_t>::max()));
std::vector<std::pair<size_t, size_t>> map_infill_end_point_to_boundary;
static constexpr auto boundary_idx_unconnected = std::numeric_limits<size_t>::max();
map_infill_end_point_to_boundary.assign(infill_ordered.size() * 2, std::pair<size_t, size_t>(boundary_idx_unconnected, boundary_idx_unconnected));
{
// Project the infill_ordered end points onto boundary_src.
std::vector<std::pair<EdgeGrid::Grid::ClosestPointResult, size_t>> intersection_points;
@ -895,13 +899,14 @@ void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_
contour_data.front().param = contour_data.back().param + (contour_dst.back().cast<float>() - contour_dst.front().cast<float>()).norm();
}
#ifndef NDEBUG
assert(boundary.size() == boundary_src.num_contours());
assert(std::all_of(map_infill_end_point_to_boundary.begin(), map_infill_end_point_to_boundary.end(),
#if 0
// Adaptive Cubic Infill produces infill lines, which not always end at the outer boundary.
assert(std::all_of(map_infill_end_point_to_boundary.begin(), map_infill_end_point_to_boundary.end(),
[&boundary](const std::pair<size_t, size_t> &contour_point) {
return contour_point.first < boundary.size() && contour_point.second < boundary[contour_point.first].size();
}));
#endif /* NDEBUG */
#endif
}
// Mark the points and segments of split boundary as consumed if they are very close to some of the infill line.
@ -932,9 +937,9 @@ void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_
const Polyline &pl2 = infill_ordered[idx_chain];
const std::pair<size_t, size_t> *cp1 = &map_infill_end_point_to_boundary[(idx_chain - 1) * 2 + 1];
const std::pair<size_t, size_t> *cp2 = &map_infill_end_point_to_boundary[idx_chain * 2];
const std::vector<ContourPointData> &contour_data = boundary_data[cp1->first];
if (cp1->first == cp2->first) {
if (cp1->first != boundary_idx_unconnected && cp1->first == cp2->first) {
// End points on the same contour. Try to connect them.
const std::vector<ContourPointData> &contour_data = boundary_data[cp1->first];
float param_lo = (cp1->second == 0) ? 0.f : contour_data[cp1->second].param;
float param_hi = (cp2->second == 0) ? 0.f : contour_data[cp2->second].param;
float param_end = contour_data.front().param;
@ -961,7 +966,7 @@ void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_
const std::pair<size_t, size_t> *cp1prev = cp1 - 1;
const std::pair<size_t, size_t> *cp2 = &map_infill_end_point_to_boundary[(connection_cost.idx_first + 1) * 2];
const std::pair<size_t, size_t> *cp2next = cp2 + 1;
assert(cp1->first == cp2->first);
assert(cp1->first == cp2->first && cp1->first != boundary_idx_unconnected);
std::vector<ContourPointData> &contour_data = boundary_data[cp1->first];
if (connection_cost.reversed)
std::swap(cp1, cp2);

View file

@ -11,6 +11,7 @@
#include "../libslic3r.h"
#include "../BoundingBox.hpp"
#include "../Exception.hpp"
#include "../Utils.hpp"
namespace Slic3r {
@ -19,9 +20,14 @@ class ExPolygon;
class Surface;
enum InfillPattern : int;
class InfillFailedException : public std::runtime_error {
namespace FillAdaptive {
struct Octree;
};
// Infill shall never fail, therefore the error is classified as RuntimeError, not SlicingError.
class InfillFailedException : public Slic3r::RuntimeError {
public:
InfillFailedException() : std::runtime_error("Infill failed") {}
InfillFailedException() : Slic3r::RuntimeError("Infill failed") {}
};
struct FillParams
@ -69,6 +75,9 @@ public:
// In scaled coordinates. Bounding box of the 2D projection of the object.
BoundingBox bounding_box;
// Octree builds on mesh for usage in the adaptive cubic infill
FillAdaptive::Octree* adapt_fill_octree = nullptr;
public:
virtual ~Fill() {}

View file

@ -53,7 +53,7 @@ static inline FlowRole opt_key_to_flow_role(const std::string &opt_key)
else if (opt_key == "support_material_extrusion_width")
return frSupportMaterial;
else
throw std::runtime_error("opt_key_to_flow_role: invalid argument");
throw Slic3r::RuntimeError("opt_key_to_flow_role: invalid argument");
};
static inline void throw_on_missing_variable(const std::string &opt_key, const char *dependent_opt_key)
@ -126,7 +126,7 @@ Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent
{
// we need layer height unless it's a bridge
if (height <= 0 && bridge_flow_ratio == 0)
throw std::invalid_argument("Invalid flow height supplied to new_from_config_width()");
throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_config_width()");
float w;
if (bridge_flow_ratio > 0) {
@ -151,7 +151,7 @@ Flow Flow::new_from_spacing(float spacing, float nozzle_diameter, float height,
{
// we need layer height unless it's a bridge
if (height <= 0 && !bridge)
throw std::invalid_argument("Invalid flow height supplied to new_from_spacing()");
throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_spacing()");
// Calculate width from spacing.
// For normal extrusons, extrusion width is wider than the spacing due to the rounding and squishing of the extrusions.
// For bridge extrusions, the extrusions are placed with a tiny BRIDGE_EXTRA_SPACING gaps between the threads.

View file

@ -3,6 +3,7 @@
#include "libslic3r.h"
#include "Config.hpp"
#include "Exception.hpp"
#include "ExtrusionEntity.hpp"
namespace Slic3r {
@ -27,11 +28,11 @@ enum FlowRole {
frSupportMaterialInterface,
};
class FlowError : public std::invalid_argument
class FlowError : public Slic3r::InvalidArgument
{
public:
FlowError(const std::string& what_arg) : invalid_argument(what_arg) {}
FlowError(const char* what_arg) : invalid_argument(what_arg) {}
FlowError(const std::string& what_arg) : Slic3r::InvalidArgument(what_arg) {}
FlowError(const char* what_arg) : Slic3r::InvalidArgument(what_arg) {}
};
class FlowErrorNegativeSpacing : public FlowError

View file

@ -1,4 +1,5 @@
#include "../libslic3r.h"
#include "../Exception.hpp"
#include "../Model.hpp"
#include "../Utils.hpp"
#include "../GCode.hpp"
@ -123,11 +124,11 @@ const char* INVALID_OBJECT_TYPES[] =
"other"
};
class version_error : public std::runtime_error
class version_error : public Slic3r::FileIOError
{
public:
version_error(const std::string& what_arg) : std::runtime_error(what_arg) {}
version_error(const char* what_arg) : std::runtime_error(what_arg) {}
version_error(const std::string& what_arg) : Slic3r::FileIOError(what_arg) {}
version_error(const char* what_arg) : Slic3r::FileIOError(what_arg) {}
};
const char* get_attribute_value_charptr(const char** attributes, unsigned int attributes_size, const char* attribute_key)
@ -607,7 +608,7 @@ namespace Slic3r {
{
// ensure the zip archive is closed and rethrow the exception
close_zip_reader(&archive);
throw std::runtime_error(e.what());
throw Slic3r::FileIOError(e.what());
}
}
}
@ -780,7 +781,7 @@ namespace Slic3r {
{
char error_buf[1024];
::sprintf(error_buf, "Error (%s) while parsing '%s' at line %d", XML_ErrorString(XML_GetErrorCode(data->parser)), data->stat.m_filename, (int)XML_GetCurrentLineNumber(data->parser));
throw std::runtime_error(error_buf);
throw Slic3r::FileIOError(error_buf);
}
return n;
@ -789,7 +790,7 @@ namespace Slic3r {
catch (const version_error& e)
{
// rethrow the exception
throw std::runtime_error(e.what());
throw Slic3r::FileIOError(e.what());
}
catch (std::exception& e)
{
@ -2360,9 +2361,9 @@ namespace Slic3r {
continue;
if (!volume->mesh().repaired)
throw std::runtime_error("store_3mf() requires repair()");
throw Slic3r::FileIOError("store_3mf() requires repair()");
if (!volume->mesh().has_shared_vertices())
throw std::runtime_error("store_3mf() requires shared vertices");
throw Slic3r::FileIOError("store_3mf() requires shared vertices");
volumes_offsets.insert(VolumeToOffsetsMap::value_type(volume, Offsets(vertices_count))).first;

View file

@ -7,6 +7,7 @@
#include <boost/nowide/cstdio.hpp>
#include "../libslic3r.h"
#include "../Exception.hpp"
#include "../Model.hpp"
#include "../GCode.hpp"
#include "../PrintConfig.hpp"
@ -923,7 +924,7 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi
{
char error_buf[1024];
::sprintf(error_buf, "Error (%s) while parsing '%s' at line %d", XML_ErrorString(XML_GetErrorCode(data->parser)), data->stat.m_filename, (int)XML_GetCurrentLineNumber(data->parser));
throw std::runtime_error(error_buf);
throw Slic3r::FileIOError(error_buf);
}
return n;
@ -948,9 +949,9 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi
if (check_version && (ctx.m_version > VERSION_AMF_COMPATIBLE))
{
// std::string msg = _(L("The selected amf file has been saved with a newer version of " + std::string(SLIC3R_APP_NAME) + " and is not compatible."));
// throw std::runtime_error(msg.c_str());
// throw Slic3r::FileIOError(msg.c_str());
const std::string msg = (boost::format(_(L("The selected amf file has been saved with a newer version of %1% and is not compatible."))) % std::string(SLIC3R_APP_NAME)).str();
throw std::runtime_error(msg);
throw Slic3r::FileIOError(msg);
}
return true;
@ -994,7 +995,7 @@ bool load_amf_archive(const char* path, DynamicPrintConfig* config, Model* model
{
// ensure the zip archive is closed and rethrow the exception
close_zip_reader(&archive);
throw std::runtime_error(e.what());
throw Slic3r::FileIOError(e.what());
}
break;
@ -1147,9 +1148,9 @@ bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config,
for (ModelVolume *volume : object->volumes) {
vertices_offsets.push_back(num_vertices);
if (! volume->mesh().repaired)
throw std::runtime_error("store_amf() requires repair()");
throw Slic3r::FileIOError("store_amf() requires repair()");
if (! volume->mesh().has_shared_vertices())
throw std::runtime_error("store_amf() requires shared vertices");
throw Slic3r::FileIOError("store_amf() requires shared vertices");
const indexed_triangle_set &its = volume->mesh().its;
const Transform3d& matrix = volume->get_matrix();
for (size_t i = 0; i < its.vertices.size(); ++i) {

View file

@ -147,7 +147,7 @@ static void extract_model_from_archive(
}
}
if (! trafo_set)
throw std::runtime_error(std::string("Archive ") + path + " does not contain a valid entry in scene.xml for " + name);
throw Slic3r::FileIOError(std::string("Archive ") + path + " does not contain a valid entry in scene.xml for " + name);
// Extract the STL.
StlHeader header;
@ -266,7 +266,7 @@ static void extract_model_from_archive(
}
if (! mesh_valid)
throw std::runtime_error(std::string("Archive ") + path + " does not contain a valid mesh for " + name);
throw Slic3r::FileIOError(std::string("Archive ") + path + " does not contain a valid mesh for " + name);
// Add this mesh to the model.
ModelVolume *volume = nullptr;
@ -303,7 +303,7 @@ bool load_prus(const char *path, Model *model)
mz_bool res = MZ_FALSE;
try {
if (!open_zip_reader(&archive, path))
throw std::runtime_error(std::string("Unable to init zip reader for ") + path);
throw Slic3r::FileIOError(std::string("Unable to init zip reader for ") + path);
std::vector<char> scene_xml_data;
// For grouping multiple STLs into a single ModelObject for multi-material prints.
std::map<int, ModelObject*> group_to_model_object;
@ -316,10 +316,10 @@ bool load_prus(const char *path, Model *model)
buffer.assign((size_t)stat.m_uncomp_size, 0);
res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (char*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
if (res == MZ_FALSE)
std::runtime_error(std::string("Error while extracting a file from ") + path);
throw Slic3r::FileIOError(std::string("Error while extracting a file from ") + path);
if (strcmp(stat.m_filename, "scene.xml") == 0) {
if (! scene_xml_data.empty())
throw std::runtime_error(std::string("Multiple scene.xml were found in the archive.") + path);
throw Slic3r::FileIOError(std::string("Multiple scene.xml were found in the archive.") + path);
scene_xml_data = std::move(buffer);
} else if (boost::iends_with(stat.m_filename, ".stl")) {
// May throw std::exception

View file

@ -10,6 +10,7 @@
#include <sstream>
#include "libslic3r/Exception.hpp"
#include "libslic3r/SlicesToTriangleMesh.hpp"
#include "libslic3r/MarchingSquares.hpp"
#include "libslic3r/ClipperUtils.hpp"
@ -64,7 +65,7 @@ boost::property_tree::ptree read_ini(const mz_zip_archive_file_stat &entry,
if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename,
buf.data(), buf.size(), 0))
throw std::runtime_error(zip.get_errorstr());
throw Slic3r::FileIOError(zip.get_errorstr());
boost::property_tree::ptree tree;
std::stringstream ss(buf);
@ -80,7 +81,7 @@ PNGBuffer read_png(const mz_zip_archive_file_stat &entry,
if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename,
buf.data(), buf.size(), 0))
throw std::runtime_error(zip.get_errorstr());
throw Slic3r::FileIOError(zip.get_errorstr());
return {std::move(buf), (name.empty() ? entry.m_filename : name)};
}
@ -94,7 +95,7 @@ ArchiveData extract_sla_archive(const std::string &zipfname,
struct Arch: public MZ_Archive {
Arch(const std::string &fname) {
if (!open_zip_reader(&arch, fname))
throw std::runtime_error(get_errorstr());
throw Slic3r::FileIOError(get_errorstr());
}
~Arch() { close_zip_reader(&arch); }
@ -202,7 +203,7 @@ RasterParams get_raster_params(const DynamicPrintConfig &cfg)
if (!opt_disp_cols || !opt_disp_rows || !opt_disp_w || !opt_disp_h ||
!opt_mirror_x || !opt_mirror_y || !opt_orient)
throw std::runtime_error("Invalid SL1 file");
throw Slic3r::FileIOError("Invalid SL1 file");
RasterParams rstp;
@ -228,7 +229,7 @@ SliceParams get_slice_params(const DynamicPrintConfig &cfg)
auto *opt_init_layerh = cfg.option<ConfigOptionFloat>("initial_layer_height");
if (!opt_layerh || !opt_init_layerh)
throw std::runtime_error("Invalid SL1 file");
throw Slic3r::FileIOError("Invalid SL1 file");
return SliceParams{opt_layerh->getFloat(), opt_init_layerh->getFloat()};
}

View file

@ -1,6 +1,7 @@
#include "libslic3r.h"
#include "I18N.hpp"
#include "GCode.hpp"
#include "Exception.hpp"
#include "ExtrusionEntity.hpp"
#include "EdgeGrid.hpp"
#include "Geometry.hpp"
@ -287,7 +288,7 @@ namespace Slic3r {
std::string WipeTowerIntegration::append_tcr(GCode& gcodegen, const WipeTower::ToolChangeResult& tcr, int new_extruder_id, double z) const
{
if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool)
throw std::invalid_argument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect.");
throw Slic3r::InvalidArgument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect.");
std::string gcode;
@ -540,7 +541,7 @@ namespace Slic3r {
if (!m_brim_done || gcodegen.writer().need_toolchange(extruder_id) || finish_layer) {
if (m_layer_idx < (int)m_tool_changes.size()) {
if (!(size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size()))
throw std::runtime_error("Wipe tower generation failed, possibly due to empty first layer.");
throw Slic3r::RuntimeError("Wipe tower generation failed, possibly due to empty first layer.");
// Calculate where the wipe tower layer will be printed. -1 means that print z will not change,
@ -629,7 +630,7 @@ std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObjec
// Check that there are extrusions on the very first layer.
if (layers_to_print.size() == 1u) {
if (!has_extrusions)
throw std::runtime_error(_(L("There is an object with no extrusions on the first layer.")));
throw Slic3r::RuntimeError(_(L("There is an object with no extrusions on the first layer.")));
}
// In case there are extrusions on this layer, check there is a layer to lay it on.
@ -721,9 +722,9 @@ namespace DoExport {
static void update_print_estimated_times_stats(const GCodeProcessor& processor, PrintStatistics& print_statistics)
{
const GCodeProcessor::Result& result = processor.get_result();
print_statistics.estimated_normal_print_time = get_time_dhm(result.time_statistics.modes[static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Normal)].time);
print_statistics.estimated_normal_print_time = get_time_dhms(result.time_statistics.modes[static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Normal)].time);
print_statistics.estimated_silent_print_time = processor.is_stealth_time_estimator_enabled() ?
get_time_dhm(result.time_statistics.modes[static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].time) : "N/A";
get_time_dhms(result.time_statistics.modes[static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].time) : "N/A";
}
} // namespace DoExport
@ -750,7 +751,7 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_
FILE *file = boost::nowide::fopen(path_tmp.c_str(), "wb");
if (file == nullptr)
throw std::runtime_error(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n");
throw Slic3r::RuntimeError(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n");
#if !ENABLE_GCODE_VIEWER
m_enable_analyzer = preview_data != nullptr;
@ -763,7 +764,7 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_
if (ferror(file)) {
fclose(file);
boost::nowide::remove(path_tmp.c_str());
throw std::runtime_error(std::string("G-code export to ") + path + " failed\nIs the disk full?\n");
throw Slic3r::RuntimeError(std::string("G-code export to ") + path + " failed\nIs the disk full?\n");
}
} catch (std::exception & /* ex */) {
// Rethrow on any exception. std::runtime_exception and CanceledException are expected to be thrown.
@ -784,14 +785,16 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_
msg += " !!!!! Failed to process the custom G-code template ...\n";
msg += "and\n";
msg += " !!!!! End of an error report for the custom G-code template ...\n";
throw std::runtime_error(msg);
throw Slic3r::RuntimeError(msg);
}
#if ENABLE_GCODE_VIEWER
m_processor.process_file(path_tmp);
BOOST_LOG_TRIVIAL(debug) << "Start processing gcode, " << log_memory_info();
m_processor.process_file(path_tmp, [print]() { print->throw_if_canceled(); });
DoExport::update_print_estimated_times_stats(m_processor, print->m_print_statistics);
if (result != nullptr)
*result = std::move(m_processor.extract_result());
BOOST_LOG_TRIVIAL(debug) << "Finished processing gcode, " << log_memory_info();
#else
GCodeTimeEstimator::PostProcessData normal_data = m_normal_time_estimator.get_post_process_data();
GCodeTimeEstimator::PostProcessData silent_data = m_silent_time_estimator.get_post_process_data();
@ -816,7 +819,7 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_
#endif // ENABLE_GCODE_VIEWER
if (rename_file(path_tmp, path))
throw std::runtime_error(
throw Slic3r::RuntimeError(
std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + path + '\n' +
"Is " + path_tmp + " locked?" + '\n');
@ -1944,8 +1947,8 @@ namespace ProcessLayer
#if !ENABLE_GCODE_VIEWER
// add tag for time estimator
gcode += "; " + GCodeTimeEstimator::Pause_Print_Tag + "\n";
gcode += config.pause_print_gcode;
#endif // !ENABLE_GCODE_VIEWER
gcode += config.pause_print_gcode;
}
else
{
@ -2457,14 +2460,17 @@ void GCode::process_layer(
#endif /* HAS_PRESSURE_EQUALIZER */
_write(file, gcode);
#if !ENABLE_GCODE_VIEWER
#if ENABLE_GCODE_VIEWER
BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z <<
log_memory_info();
#else
BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z <<
", time estimator memory: " <<
format_memsize_MB(m_normal_time_estimator.memory_used() + (m_silent_time_estimator_enabled ? m_silent_time_estimator.memory_used() : 0)) <<
", analyzer memory: " <<
format_memsize_MB(m_analyzer.memory_used()) <<
log_memory_info();
#endif // !ENABLE_GCODE_VIEWER
format_memsize_MB(m_normal_time_estimator.memory_used() + (m_silent_time_estimator_enabled ? m_silent_time_estimator.memory_used() : 0)) <<
", analyzer memory: " <<
format_memsize_MB(m_analyzer.memory_used()) <<
log_memory_info();
#endif // ENABLE_GCODE_VIEWER
}
void GCode::apply_print_config(const PrintConfig &print_config)
@ -2704,7 +2710,7 @@ std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string des
else if (const ExtrusionLoop* loop = dynamic_cast<const ExtrusionLoop*>(&entity))
return this->extrude_loop(*loop, description, speed, lower_layer_edge_grid);
else
throw std::invalid_argument("Invalid argument supplied to extrude()");
throw Slic3r::InvalidArgument("Invalid argument supplied to extrude()");
return "";
}
@ -2909,7 +2915,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
} else if (path.role() == erGapFill) {
speed = m_config.get_abs_value("gap_fill_speed");
} else {
throw std::invalid_argument("Invalid speed");
throw Slic3r::InvalidArgument("Invalid speed");
}
}
if (this->on_first_layer())
@ -3330,7 +3336,7 @@ void GCode::ObjectByExtruder::Island::Region::append(const Type type, const Extr
perimeters_or_infills_overrides = &infills_overrides;
break;
default:
throw std::invalid_argument("Unknown parameter!");
throw Slic3r::InvalidArgument("Unknown parameter!");
}
// First we append the entities, there are eec->entities.size() of them:

View file

@ -11,10 +11,7 @@
#include <assert.h>
#if ENABLE_GCODE_VIEWER
#if ENABLE_GCODE_VIEWER_STATISTICS
#include <chrono>
#endif // ENABLE_GCODE_VIEWER_STATISTICS
static const float INCHES_TO_MM = 25.4f;
static const float MMMIN_TO_MMSEC = 1.0f / 60.0f;
@ -322,13 +319,13 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename)
{
boost::nowide::ifstream in(filename);
if (!in.good())
throw std::runtime_error(std::string("Time estimator post process export failed.\nCannot open file for reading.\n"));
throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for reading.\n"));
// temporary file to contain modified gcode
std::string out_path = filename + ".postprocess";
FILE* out = boost::nowide::fopen(out_path.c_str(), "wb");
if (out == nullptr)
throw std::runtime_error(std::string("Time estimator post process export failed.\nCannot open file for writing.\n"));
throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for writing.\n"));
auto time_in_minutes = [](float time_in_seconds) {
return int(::roundf(time_in_seconds / 60.0f));
@ -421,7 +418,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename)
in.close();
fclose(out);
boost::nowide::remove(out_path.c_str());
throw std::runtime_error(std::string("Time estimator post process export failed.\nIs the disk full?\n"));
throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nIs the disk full?\n"));
}
export_line.clear();
};
@ -429,7 +426,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename)
while (std::getline(in, gcode_line)) {
if (!in.good()) {
fclose(out);
throw std::runtime_error(std::string("Time estimator post process export failed.\nError while reading from file.\n"));
throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nError while reading from file.\n"));
}
gcode_line += "\n";
@ -463,7 +460,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename)
in.close();
if (rename_file(out_path, filename))
throw std::runtime_error(std::string("Failed to rename the output G-code file from ") + out_path + " to " + filename + '\n' +
throw Slic3r::RuntimeError(std::string("Failed to rename the output G-code file from ") + out_path + " to " + filename + '\n' +
"Is " + out_path + " locked?" + '\n');
}
@ -730,8 +727,10 @@ void GCodeProcessor::reset()
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
}
void GCodeProcessor::process_file(const std::string& filename)
void GCodeProcessor::process_file(const std::string& filename, std::function<void()> cancel_callback)
{
auto last_cancel_callback_time = std::chrono::high_resolution_clock::now();
#if ENABLE_GCODE_VIEWER_STATISTICS
auto start_time = std::chrono::high_resolution_clock::now();
#endif // ENABLE_GCODE_VIEWER_STATISTICS
@ -758,9 +757,21 @@ void GCodeProcessor::process_file(const std::string& filename)
}
}
// process gcode
m_result.id = ++s_result_id;
// 1st move must be a dummy move
m_result.moves.emplace_back(MoveVertex());
m_parser.parse_file(filename, [this](GCodeReader& reader, const GCodeReader::GCodeLine& line) { process_gcode_line(line); });
m_parser.parse_file(filename, [this, cancel_callback, &last_cancel_callback_time](GCodeReader& reader, const GCodeReader::GCodeLine& line) {
if (cancel_callback != nullptr) {
// call the cancel callback every 100 ms
auto curr_time = std::chrono::high_resolution_clock::now();
if (std::chrono::duration_cast<std::chrono::milliseconds>(curr_time - last_cancel_callback_time).count() > 100) {
cancel_callback();
last_cancel_callback_time = curr_time;
}
}
process_gcode_line(line);
});
// process the time blocks
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
@ -936,6 +947,20 @@ void GCodeProcessor::process_tags(const std::string& comment)
return;
}
if (!m_producers_enabled || m_producer == EProducer::PrusaSlicer) {
// height tag
pos = comment.find(Height_Tag);
if (pos != comment.npos) {
try {
m_height = std::stof(comment.substr(pos + Height_Tag.length()));
}
catch (...) {
BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ").";
}
return;
}
}
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
// width tag
pos = comment.find(Width_Tag);
@ -948,18 +973,6 @@ void GCodeProcessor::process_tags(const std::string& comment)
}
return;
}
// height tag
pos = comment.find(Height_Tag);
if (pos != comment.npos) {
try {
m_height_compare.last_tag_value = std::stof(comment.substr(pos + Height_Tag.length()));
}
catch (...) {
BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ").";
}
return;
}
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
// color change tag
@ -1408,12 +1421,12 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
type = EMoveType::Travel;
if (type == EMoveType::Extrude) {
float d_xyz = std::sqrt(sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z]));
float delta_xyz = std::sqrt(sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z]));
float filament_diameter = (static_cast<size_t>(m_extruder_id) < m_filament_diameters.size()) ? m_filament_diameters[m_extruder_id] : m_filament_diameters.back();
float filament_radius = 0.5f * filament_diameter;
float area_filament_cross_section = static_cast<float>(M_PI) * sqr(filament_radius);
float volume_extruded_filament = area_filament_cross_section * delta_pos[E];
float area_toolpath_cross_section = volume_extruded_filament / d_xyz;
float area_toolpath_cross_section = volume_extruded_filament / delta_xyz;
// volume extruded filament / tool displacement = area toolpath cross section
m_mm3_per_mm = area_toolpath_cross_section;
@ -1421,23 +1434,28 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
m_mm3_per_mm_compare.update(area_toolpath_cross_section, m_extrusion_role);
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
if (m_end_position[Z] > m_extruded_last_z + EPSILON) {
m_height = m_end_position[Z] - m_extruded_last_z;
if ((m_producers_enabled && m_producer != EProducer::PrusaSlicer) || m_height == 0.0f) {
if (m_end_position[Z] > m_extruded_last_z + EPSILON) {
m_height = m_end_position[Z] - m_extruded_last_z;
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
m_height_compare.update(m_height, m_extrusion_role);
m_height_compare.update(m_height, m_extrusion_role);
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
m_extruded_last_z = m_end_position[Z];
m_extruded_last_z = m_end_position[Z];
}
}
if (m_extrusion_role == erExternalPerimeter)
// cross section: rectangle
m_width = delta_pos[E] * static_cast<float>(M_PI * sqr(1.05 * filament_radius)) / (d_xyz * m_height);
m_width = delta_pos[E] * static_cast<float>(M_PI * sqr(1.05 * filament_radius)) / (delta_xyz * m_height);
else if (m_extrusion_role == erBridgeInfill || m_extrusion_role == erNone)
// cross section: circle
m_width = static_cast<float>(m_filament_diameters[m_extruder_id]) * std::sqrt(delta_pos[E] / d_xyz);
m_width = static_cast<float>(m_filament_diameters[m_extruder_id]) * std::sqrt(delta_pos[E] / delta_xyz);
else
// cross section: rectangle + 2 semicircles
m_width = delta_pos[E] * static_cast<float>(M_PI * sqr(filament_radius)) / (d_xyz * m_height) + static_cast<float>(1.0 - 0.25 * M_PI) * m_height;
m_width = delta_pos[E] * static_cast<float>(M_PI * sqr(filament_radius)) / (delta_xyz * m_height) + static_cast<float>(1.0 - 0.25 * M_PI) * m_height;
// clamp width to avoid artifacts which may arise from wrong values of m_height
m_width = std::min(m_width, 4.0f * m_height);
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
m_width_compare.update(m_width, m_extrusion_role);

View file

@ -419,7 +419,8 @@ namespace Slic3r {
Result&& extract_result() { return std::move(m_result); }
// Process the gcode contained in the file with the given filename
void process_file(const std::string& filename);
// throws CanceledException through print->throw_if_canceled() (sent by the caller as callback).
void process_file(const std::string& filename, std::function<void()> cancel_callback = nullptr);
float get_time(PrintEstimatedTimeStatistics::ETimeMode mode) const;
std::string get_time_dhm(PrintEstimatedTimeStatistics::ETimeMode mode) const;

View file

@ -79,7 +79,7 @@ static DWORD execute_process_winapi(const std::wstring &command_line)
if (! ::CreateProcessW(
nullptr /* lpApplicationName */, (LPWSTR)command_line.c_str(), nullptr /* lpProcessAttributes */, nullptr /* lpThreadAttributes */, false /* bInheritHandles */,
CREATE_UNICODE_ENVIRONMENT /* | CREATE_NEW_CONSOLE */ /* dwCreationFlags */, (LPVOID)envstr.c_str(), nullptr /* lpCurrentDirectory */, &startup_info, &process_info))
throw std::runtime_error(std::string("Failed starting the script ") + boost::nowide::narrow(command_line) + ", Win32 error: " + std::to_string(int(::GetLastError())));
throw Slic3r::RuntimeError(std::string("Failed starting the script ") + boost::nowide::narrow(command_line) + ", Win32 error: " + std::to_string(int(::GetLastError())));
::WaitForSingleObject(process_info.hProcess, INFINITE);
ULONG rc = 0;
::GetExitCodeProcess(process_info.hProcess, &rc);
@ -98,13 +98,13 @@ static int run_script(const std::string &script, const std::string &gcode, std::
LPWSTR *szArglist = CommandLineToArgvW(boost::nowide::widen(script).c_str(), &nArgs);
if (szArglist == nullptr || nArgs <= 0) {
// CommandLineToArgvW failed. Maybe the command line escapment is invalid?
throw std::runtime_error(std::string("Post processing script ") + script + " on file " + gcode + " failed. CommandLineToArgvW() refused to parse the command line path.");
throw Slic3r::RuntimeError(std::string("Post processing script ") + script + " on file " + gcode + " failed. CommandLineToArgvW() refused to parse the command line path.");
}
std::wstring command_line;
std::wstring command = szArglist[0];
if (! boost::filesystem::exists(boost::filesystem::path(command)))
throw std::runtime_error(std::string("The configured post-processing script does not exist: ") + boost::nowide::narrow(command));
throw Slic3r::RuntimeError(std::string("The configured post-processing script does not exist: ") + boost::nowide::narrow(command));
if (boost::iends_with(command, L".pl")) {
// This is a perl script. Run it through the perl interpreter.
// The current process may be slic3r.exe or slic3r-console.exe.
@ -115,7 +115,7 @@ static int run_script(const std::string &script, const std::string &gcode, std::
boost::filesystem::path path_perl = path_exe.parent_path() / "perl" / "perl.exe";
if (! boost::filesystem::exists(path_perl)) {
LocalFree(szArglist);
throw std::runtime_error(std::string("Perl interpreter ") + path_perl.string() + " does not exist.");
throw Slic3r::RuntimeError(std::string("Perl interpreter ") + path_perl.string() + " does not exist.");
}
// Replace it with the current perl interpreter.
quote_argv_winapi(boost::nowide::widen(path_perl.string()), command_line);
@ -187,7 +187,7 @@ void run_post_process_scripts(const std::string &path, const PrintConfig &config
config.setenv_();
auto gcode_file = boost::filesystem::path(path);
if (! boost::filesystem::exists(gcode_file))
throw std::runtime_error(std::string("Post-processor can't find exported gcode file"));
throw Slic3r::RuntimeError(std::string("Post-processor can't find exported gcode file"));
for (const std::string &scripts : config.post_process.values) {
std::vector<std::string> lines;
@ -205,7 +205,7 @@ void run_post_process_scripts(const std::string &path, const PrintConfig &config
const std::string msg = std_err.empty() ? (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%") % script % path % result).str()
: (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%\nOutput:\n%4%") % script % path % result % std_err).str();
BOOST_LOG_TRIVIAL(error) << msg;
throw std::runtime_error(msg);
throw Slic3r::RuntimeError(msg);
}
}
}

View file

@ -148,7 +148,7 @@ static inline int parse_int(const char *&line)
char *endptr = NULL;
long result = strtol(line, &endptr, 10);
if (endptr == NULL || !is_ws_or_eol(*endptr))
throw std::runtime_error("PressureEqualizer: Error parsing an int");
throw Slic3r::RuntimeError("PressureEqualizer: Error parsing an int");
line = endptr;
return int(result);
};
@ -160,7 +160,7 @@ static inline float parse_float(const char *&line)
char *endptr = NULL;
float result = strtof(line, &endptr);
if (endptr == NULL || !is_ws_or_eol(*endptr))
throw std::runtime_error("PressureEqualizer: Error parsing a float");
throw Slic3r::RuntimeError("PressureEqualizer: Error parsing a float");
line = endptr;
return result;
};
@ -229,7 +229,7 @@ bool PressureEqualizer::process_line(const char *line, const size_t len, GCodeLi
assert(false);
}
if (i == -1)
throw std::runtime_error(std::string("GCode::PressureEqualizer: Invalid axis for G0/G1: ") + axis);
throw Slic3r::RuntimeError(std::string("GCode::PressureEqualizer: Invalid axis for G0/G1: ") + axis);
buf.pos_provided[i] = true;
new_pos[i] = parse_float(line);
if (i == 3 && m_config->use_relative_e_distances.value)
@ -298,7 +298,7 @@ bool PressureEqualizer::process_line(const char *line, const size_t len, GCodeLi
set = true;
break;
default:
throw std::runtime_error(std::string("GCode::PressureEqualizer: Incorrect axis in a G92 G-code: ") + axis);
throw Slic3r::RuntimeError(std::string("GCode::PressureEqualizer: Incorrect axis in a G92 G-code: ") + axis);
}
eatws(line);
}

View file

@ -94,7 +94,7 @@ static BoundingBoxf extrusionentity_extents(const ExtrusionEntity *extrusion_ent
auto *extrusion_entity_collection = dynamic_cast<const ExtrusionEntityCollection*>(extrusion_entity);
if (extrusion_entity_collection != nullptr)
return extrusionentity_extents(*extrusion_entity_collection);
throw std::runtime_error("Unexpected extrusion_entity type in extrusionentity_extents()");
throw Slic3r::RuntimeError("Unexpected extrusion_entity type in extrusionentity_extents()");
return BoundingBoxf();
}

View file

@ -1,6 +1,9 @@
#include "GCodeReader.hpp"
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
#if ENABLE_GCODE_VIEWER
#include <boost/nowide/fstream.hpp>
#endif // ENABLE_GCODE_VIEWER
#include <fstream>
#include <iostream>
#include <iomanip>
@ -113,7 +116,11 @@ void GCodeReader::update_coordinates(GCodeLine &gline, std::pair<const char*, co
void GCodeReader::parse_file(const std::string &file, callback_t callback)
{
#if ENABLE_GCODE_VIEWER
boost::nowide::ifstream f(file);
#else
std::ifstream f(file);
#endif // ENABLE_GCODE_VIEWER
std::string line;
#if ENABLE_GCODE_VIEWER
m_parsing_file = true;

View file

@ -153,7 +153,7 @@ GCodeSender::set_baud_rate(unsigned int baud_rate)
if (::tcsetattr(handle, TCSAFLUSH, &ios) != 0)
printf("Failed to set baud rate: %s\n", strerror(errno));
#else
//throw invalid_argument ("OS does not currently support custom bauds");
//throw Slic3r::InvalidArgument("OS does not currently support custom bauds");
#endif
}
}

View file

@ -1,3 +1,4 @@
#include "Exception.hpp"
#include "GCodeTimeEstimator.hpp"
#include "Utils.hpp"
#include <boost/bind.hpp>
@ -254,13 +255,13 @@ namespace Slic3r {
{
boost::nowide::ifstream in(filename);
if (!in.good())
throw std::runtime_error(std::string("Time estimator post process export failed.\nCannot open file for reading.\n"));
throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for reading.\n"));
std::string path_tmp = filename + ".postprocess";
FILE* out = boost::nowide::fopen(path_tmp.c_str(), "wb");
if (out == nullptr)
throw std::runtime_error(std::string("Time estimator post process export failed.\nCannot open file for writing.\n"));
throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for writing.\n"));
std::string normal_time_mask = "M73 P%s R%s\n";
std::string silent_time_mask = "M73 Q%s S%s\n";
@ -278,7 +279,7 @@ namespace Slic3r {
in.close();
fclose(out);
boost::nowide::remove(path_tmp.c_str());
throw std::runtime_error(std::string("Time estimator post process export failed.\nIs the disk full?\n"));
throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nIs the disk full?\n"));
}
export_line.clear();
};
@ -326,7 +327,7 @@ namespace Slic3r {
if (!in.good())
{
fclose(out);
throw std::runtime_error(std::string("Time estimator post process export failed.\nError while reading from file.\n"));
throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nError while reading from file.\n"));
}
// check tags
@ -383,7 +384,7 @@ namespace Slic3r {
in.close();
if (rename_file(path_tmp, filename))
throw std::runtime_error(std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + filename + '\n' +
throw Slic3r::RuntimeError(std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + filename + '\n' +
"Is " + path_tmp + " locked?" + '\n');
return true;

View file

@ -1,4 +1,5 @@
#include "libslic3r.h"
#include "Exception.hpp"
#include "Geometry.hpp"
#include "ClipperUtils.hpp"
#include "ExPolygon.hpp"
@ -471,7 +472,7 @@ Pointfs arrange(size_t num_parts, const Vec2d &part_size, coordf_t gap, const Bo
size_t cellw = size_t(floor((bed_bbox.size()(0) + gap) / cell_size(0)));
size_t cellh = size_t(floor((bed_bbox.size()(1) + gap) / cell_size(1)));
if (num_parts > cellw * cellh)
throw std::invalid_argument("%zu parts won't fit in your print area!\n", num_parts);
throw Slic3r::InvalidArgument("%zu parts won't fit in your print area!\n", num_parts);
// Get a bounding box of cellw x cellh cells, centered at the center of the bed.
Vec2d cells_size(cellw * cell_size(0) - gap, cellh * cell_size(1) - gap);

View file

@ -281,7 +281,7 @@ bool directions_parallel(double angle1, double angle2, double max_diff = 0);
template<class T> bool contains(const std::vector<T> &vector, const Point &point);
template<typename T> T rad2deg(T angle) { return T(180.0) * angle / T(PI); }
double rad2deg_dir(double angle);
template<typename T> T deg2rad(T angle) { return T(PI) * angle / T(180.0); }
template<typename T> constexpr T deg2rad(const T angle) { return T(PI) * angle / T(180.0); }
template<typename T> T angle_to_0_2PI(T angle)
{
static const T TWO_PI = T(2) * T(PI);

View file

@ -13,6 +13,10 @@ class Layer;
class PrintRegion;
class PrintObject;
namespace FillAdaptive {
struct Octree;
};
class LayerRegion
{
public:
@ -134,7 +138,8 @@ public:
return false;
}
void make_perimeters();
void make_fills();
void make_fills() { this->make_fills(nullptr, nullptr); };
void make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree);
void make_ironing();
void export_region_slices_to_svg(const char *path) const;

View file

@ -1,3 +1,4 @@
#include "Exception.hpp"
#include "MeshBoolean.hpp"
#include "libslic3r/TriangleMesh.hpp"
#undef PI
@ -136,7 +137,7 @@ template<class _Mesh> void triangle_mesh_to_cgal(const TriangleMesh &M, _Mesh &o
if(CGAL::is_closed(out))
CGALProc::orient_to_bound_a_volume(out);
else
std::runtime_error("Mesh not watertight");
throw Slic3r::RuntimeError("Mesh not watertight");
}
inline Vec3d to_vec3d(const _EpicMesh::Point &v)
@ -222,7 +223,7 @@ template<class Op> void _cgal_do(Op &&op, CGALMesh &A, CGALMesh &B)
}
if (! success)
throw std::runtime_error("CGAL mesh boolean operation failed.");
throw Slic3r::RuntimeError("CGAL mesh boolean operation failed.");
}
void minus(CGALMesh &A, CGALMesh &B) { _cgal_do(_cgal_diff, A, B); }

View file

@ -1,3 +1,4 @@
#include "Exception.hpp"
#include "Model.hpp"
#include "ModelArrange.hpp"
#include "Geometry.hpp"
@ -116,13 +117,13 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c
else if (boost::algorithm::iends_with(input_file, ".prusa"))
result = load_prus(input_file.c_str(), &model);
else
throw std::runtime_error("Unknown file format. Input file must have .stl, .obj, .amf(.xml) or .prusa extension.");
throw Slic3r::RuntimeError("Unknown file format. Input file must have .stl, .obj, .amf(.xml) or .prusa extension.");
if (! result)
throw std::runtime_error("Loading of a model file failed.");
throw Slic3r::RuntimeError("Loading of a model file failed.");
if (model.objects.empty())
throw std::runtime_error("The supplied file couldn't be read because it's empty");
throw Slic3r::RuntimeError("The supplied file couldn't be read because it's empty");
for (ModelObject *o : model.objects)
o->input_file = input_file;
@ -146,13 +147,13 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig
else if (boost::algorithm::iends_with(input_file, ".zip.amf"))
result = load_amf(input_file.c_str(), config, &model, check_version);
else
throw std::runtime_error("Unknown file format. Input file must have .3mf or .zip.amf extension.");
throw Slic3r::RuntimeError("Unknown file format. Input file must have .3mf or .zip.amf extension.");
if (!result)
throw std::runtime_error("Loading of a model file failed.");
throw Slic3r::RuntimeError("Loading of a model file failed.");
if (model.objects.empty())
throw std::runtime_error("The supplied file couldn't be read because it's empty");
throw Slic3r::RuntimeError("The supplied file couldn't be read because it's empty");
for (ModelObject *o : model.objects)
{
@ -776,6 +777,38 @@ TriangleMesh ModelObject::raw_mesh() const
return mesh;
}
// Non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes.
// Currently used by ModelObject::mesh(), to calculate the 2D envelope for 2D plater
// and to display the object statistics at ModelObject::print_info().
indexed_triangle_set ModelObject::raw_indexed_triangle_set() const
{
size_t num_vertices = 0;
size_t num_faces = 0;
for (const ModelVolume *v : this->volumes)
if (v->is_model_part()) {
num_vertices += v->mesh().its.vertices.size();
num_faces += v->mesh().its.indices.size();
}
indexed_triangle_set out;
out.vertices.reserve(num_vertices);
out.indices.reserve(num_faces);
for (const ModelVolume *v : this->volumes)
if (v->is_model_part()) {
size_t i = out.vertices.size();
size_t j = out.indices.size();
append(out.vertices, v->mesh().its.vertices);
append(out.indices, v->mesh().its.indices);
auto m = v->get_matrix();
for (; i < out.vertices.size(); ++ i)
out.vertices[i] = (m * out.vertices[i].cast<double>()).cast<float>().eval();
if (v->is_left_handed()) {
for (; j < out.indices.size(); ++ j)
std::swap(out.indices[j][0], out.indices[j][1]);
}
}
return out;
}
// Non-transformed (non-rotated, non-scaled, non-translated) sum of all object volumes.
TriangleMesh ModelObject::full_raw_mesh() const
{
@ -817,7 +850,7 @@ const BoundingBoxf3& ModelObject::raw_bounding_box() const
m_raw_bounding_box_valid = true;
m_raw_bounding_box.reset();
if (this->instances.empty())
throw std::invalid_argument("Can't call raw_bounding_box() with no instances");
throw Slic3r::InvalidArgument("Can't call raw_bounding_box() with no instances");
const Transform3d& inst_matrix = this->instances.front()->get_transformation().get_matrix(true);
for (const ModelVolume *v : this->volumes)

View file

@ -244,6 +244,8 @@ public:
// Non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes.
// Currently used by ModelObject::mesh() and to calculate the 2D envelope for 2D plater.
TriangleMesh raw_mesh() const;
// The same as above, but producing a lightweight indexed_triangle_set.
indexed_triangle_set raw_indexed_triangle_set() const;
// Non-transformed (non-rotated, non-scaled, non-translated) sum of all object volumes.
TriangleMesh full_raw_mesh() const;
// A transformed snug bounding box around the non-modifier object volumes, without the translation applied.

View file

@ -20,7 +20,7 @@ using VirtualBedFn = std::function<void(arrangement::ArrangePolygon&)>;
[[noreturn]] inline void throw_if_out_of_bed(arrangement::ArrangePolygon&)
{
throw std::runtime_error("Objects could not fit on the bed");
throw Slic3r::RuntimeError("Objects could not fit on the bed");
}
ArrangePolygons get_arrange_polys(const Model &model, ModelInstancePtrs &instances);

View file

@ -2,6 +2,7 @@
#include "OpenVDBUtils.hpp"
#include <openvdb/tools/MeshToVolume.h>
#include <openvdb/tools/VolumeToMesh.h>
#include <openvdb/tools/Composite.h>
#include <openvdb/tools/LevelSetRebuild.h>
//#include "MTUtils.hpp"
@ -57,7 +58,6 @@ void Contour3DDataAdapter::getIndexSpacePoint(size_t n,
// TODO: Do I need to call initialize? Seems to work without it as well but the
// docs say it should be called ones. It does a mutex lock-unlock sequence all
// even if was called previously.
openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &mesh,
const openvdb::math::Transform &tr,
float exteriorBandWidth,
@ -65,9 +65,38 @@ openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &mesh,
int flags)
{
openvdb::initialize();
return openvdb::tools::meshToVolume<openvdb::FloatGrid>(
TriangleMeshDataAdapter{mesh}, tr, exteriorBandWidth,
interiorBandWidth, flags);
TriangleMeshPtrs meshparts = mesh.split();
auto it = std::remove_if(meshparts.begin(), meshparts.end(),
[](TriangleMesh *m){
m->require_shared_vertices();
return !m->is_manifold() || m->volume() < EPSILON;
});
meshparts.erase(it, meshparts.end());
openvdb::FloatGrid::Ptr grid;
for (TriangleMesh *m : meshparts) {
auto subgrid = openvdb::tools::meshToVolume<openvdb::FloatGrid>(
TriangleMeshDataAdapter{*m}, tr, exteriorBandWidth,
interiorBandWidth, flags);
if (grid && subgrid) openvdb::tools::csgUnion(*grid, *subgrid);
else if (subgrid) grid = std::move(subgrid);
}
if (grid) {
grid = openvdb::tools::levelSetRebuild(*grid, 0., exteriorBandWidth,
interiorBandWidth);
} else if(meshparts.empty()) {
// Splitting failed, fall back to hollow the original mesh
grid = openvdb::tools::meshToVolume<openvdb::FloatGrid>(
TriangleMeshDataAdapter{mesh}, tr, exteriorBandWidth,
interiorBandWidth, flags);
}
return grid;
}
openvdb::FloatGrid::Ptr mesh_to_grid(const sla::Contour3D &mesh,

View file

@ -0,0 +1,140 @@
#ifndef BRUTEFORCEOPTIMIZER_HPP
#define BRUTEFORCEOPTIMIZER_HPP
#include <libslic3r/Optimize/Optimizer.hpp>
namespace Slic3r { namespace opt {
namespace detail {
// Implementing a bruteforce optimizer
// Return the number of iterations needed to reach a specific grid position (idx)
template<size_t N>
long num_iter(const std::array<size_t, N> &idx, size_t gridsz)
{
long ret = 0;
for (size_t i = 0; i < N; ++i) ret += idx[i] * std::pow(gridsz, i);
return ret;
}
// Implementation of a grid search where the search interval is sampled in
// equidistant points for each dimension. Grid size determines the number of
// samples for one dimension so the number of function calls is gridsize ^ dimension.
struct AlgBurteForce {
bool to_min;
StopCriteria stc;
size_t gridsz;
AlgBurteForce(const StopCriteria &cr, size_t gs): stc{cr}, gridsz{gs} {}
// This function is called recursively for each dimension and generates
// the grid values for the particular dimension. If D is less than zero,
// the object function input values are generated for each dimension and it
// can be evaluated. The current best score is compared with the newly
// returned score and changed appropriately.
template<int D, size_t N, class Fn, class Cmp>
bool run(std::array<size_t, N> &idx,
Result<N> &result,
const Bounds<N> &bounds,
Fn &&fn,
Cmp &&cmp)
{
if (stc.stop_condition()) return false;
if constexpr (D < 0) { // Let's evaluate fn
Input<N> inp;
auto max_iter = stc.max_iterations();
if (max_iter && num_iter(idx, gridsz) >= max_iter)
return false;
for (size_t d = 0; d < N; ++d) {
const Bound &b = bounds[d];
double step = (b.max() - b.min()) / (gridsz - 1);
inp[d] = b.min() + idx[d] * step;
}
auto score = fn(inp);
if (cmp(score, result.score)) { // Change current score to the new
double absdiff = std::abs(score - result.score);
result.score = score;
result.optimum = inp;
// Check if the required precision is reached.
if (absdiff < stc.abs_score_diff() ||
absdiff < stc.rel_score_diff() * std::abs(score))
return false;
}
} else {
for (size_t i = 0; i < gridsz; ++i) {
idx[D] = i; // Mark the current grid position and dig down
if (!run<D - 1>(idx, result, bounds, std::forward<Fn>(fn),
std::forward<Cmp>(cmp)))
return false;
}
}
return true;
}
template<class Fn, size_t N>
Result<N> optimize(Fn&& fn,
const Input<N> &/*initvals*/,
const Bounds<N>& bounds)
{
std::array<size_t, N> idx = {};
Result<N> result;
if (to_min) {
result.score = std::numeric_limits<double>::max();
run<int(N) - 1>(idx, result, bounds, std::forward<Fn>(fn),
std::less<double>{});
}
else {
result.score = std::numeric_limits<double>::lowest();
run<int(N) - 1>(idx, result, bounds, std::forward<Fn>(fn),
std::greater<double>{});
}
return result;
}
};
} // namespace detail
using AlgBruteForce = detail::AlgBurteForce;
template<>
class Optimizer<AlgBruteForce> {
AlgBruteForce m_alg;
public:
Optimizer(const StopCriteria &cr = {}, size_t gridsz = 100)
: m_alg{cr, gridsz}
{}
Optimizer& to_max() { m_alg.to_min = false; return *this; }
Optimizer& to_min() { m_alg.to_min = true; return *this; }
template<class Func, size_t N>
Result<N> optimize(Func&& func,
const Input<N> &initvals,
const Bounds<N>& bounds)
{
return m_alg.optimize(std::forward<Func>(func), initvals, bounds);
}
Optimizer &set_criteria(const StopCriteria &cr)
{
m_alg.stc = cr; return *this;
}
const StopCriteria &get_criteria() const { return m_alg.stc; }
};
}} // namespace Slic3r::opt
#endif // BRUTEFORCEOPTIMIZER_HPP

View file

@ -12,134 +12,11 @@
#endif
#include <utility>
#include <tuple>
#include <array>
#include <cmath>
#include <functional>
#include <limits>
#include <cassert>
#include <libslic3r/Optimize/Optimizer.hpp>
namespace Slic3r { namespace opt {
// A type to hold the complete result of the optimization.
template<size_t N> struct Result {
int resultcode;
std::array<double, N> optimum;
double score;
};
// An interval of possible input values for optimization
class Bound {
double m_min, m_max;
public:
Bound(double min = std::numeric_limits<double>::min(),
double max = std::numeric_limits<double>::max())
: m_min(min), m_max(max)
{}
double min() const noexcept { return m_min; }
double max() const noexcept { return m_max; }
};
// Helper types for optimization function input and bounds
template<size_t N> using Input = std::array<double, N>;
template<size_t N> using Bounds = std::array<Bound, N>;
// A type for specifying the stop criteria. Setter methods can be concatenated
class StopCriteria {
// If the absolute value difference between two scores.
double m_abs_score_diff = std::nan("");
// If the relative value difference between two scores.
double m_rel_score_diff = std::nan("");
// Stop if this value or better is found.
double m_stop_score = std::nan("");
// A predicate that if evaluates to true, the optimization should terminate
// and the best result found prior to termination should be returned.
std::function<bool()> m_stop_condition = [] { return false; };
// The max allowed number of iterations.
unsigned m_max_iterations = 0;
public:
StopCriteria & abs_score_diff(double val)
{
m_abs_score_diff = val; return *this;
}
double abs_score_diff() const { return m_abs_score_diff; }
StopCriteria & rel_score_diff(double val)
{
m_rel_score_diff = val; return *this;
}
double rel_score_diff() const { return m_rel_score_diff; }
StopCriteria & stop_score(double val)
{
m_stop_score = val; return *this;
}
double stop_score() const { return m_stop_score; }
StopCriteria & max_iterations(double val)
{
m_max_iterations = val; return *this;
}
double max_iterations() const { return m_max_iterations; }
template<class Fn> StopCriteria & stop_condition(Fn &&cond)
{
m_stop_condition = cond; return *this;
}
bool stop_condition() { return m_stop_condition(); }
};
// Helper class to use optimization methods involving gradient.
template<size_t N> struct ScoreGradient {
double score;
std::optional<std::array<double, N>> gradient;
ScoreGradient(double s, const std::array<double, N> &grad)
: score{s}, gradient{grad}
{}
};
// Helper to be used in static_assert.
template<class T> struct always_false { enum { value = false }; };
// Basic interface to optimizer object
template<class Method, class Enable = void> class Optimizer {
public:
Optimizer(const StopCriteria &)
{
static_assert (always_false<Method>::value,
"Optimizer unimplemented for given method!");
}
Optimizer<Method> &to_min() { return *this; }
Optimizer<Method> &to_max() { return *this; }
Optimizer<Method> &set_criteria(const StopCriteria &) { return *this; }
StopCriteria get_criteria() const { return {}; };
template<class Func, size_t N>
Result<N> optimize(Func&& func,
const Input<N> &initvals,
const Bounds<N>& bounds) { return {}; }
// optional for randomized methods:
void seed(long /*s*/) {}
};
namespace detail {
// Helper types for NLopt algorithm selection in template contexts
@ -166,19 +43,6 @@ struct IsNLoptAlg<NLoptAlgComb<a1, a2>> {
template<class M, class T = void>
using NLoptOnly = std::enable_if_t<IsNLoptAlg<M>::value, T>;
// Helper to convert C style array to std::array. The copy should be optimized
// away with modern compilers.
template<size_t N, class T> auto to_arr(const T *a)
{
std::array<T, N> r;
std::copy(a, a + N, std::begin(r));
return r;
}
template<size_t N, class T> auto to_arr(const T (&a) [N])
{
return to_arr<N>(static_cast<const T *>(a));
}
enum class OptDir { MIN, MAX }; // Where to optimize
@ -357,23 +221,12 @@ public:
void seed(long s) { m_opt.seed(s); }
};
template<size_t N> Bounds<N> bounds(const Bound (&b) [N]) { return detail::to_arr(b); }
template<size_t N> Input<N> initvals(const double (&a) [N]) { return detail::to_arr(a); }
template<size_t N> auto score_gradient(double s, const double (&grad)[N])
{
return ScoreGradient<N>(s, detail::to_arr(grad));
}
// Predefinded NLopt algorithms that are used in the codebase
// Predefinded NLopt algorithms
using AlgNLoptGenetic = detail::NLoptAlgComb<NLOPT_GN_ESCH>;
using AlgNLoptSubplex = detail::NLoptAlg<NLOPT_LN_SBPLX>;
using AlgNLoptSimplex = detail::NLoptAlg<NLOPT_LN_NELDERMEAD>;
// TODO: define others if needed...
// Helper defs for pre-crafted global and local optimizers that work well.
using DefaultGlobalOptimizer = Optimizer<AlgNLoptGenetic>;
using DefaultLocalOptimizer = Optimizer<AlgNLoptSubplex>;
using AlgNLoptDIRECT = detail::NLoptAlg<NLOPT_GN_DIRECT>;
using AlgNLoptMLSL = detail::NLoptAlg<NLOPT_GN_MLSL>;
}} // namespace Slic3r::opt

View file

@ -0,0 +1,182 @@
#ifndef OPTIMIZER_HPP
#define OPTIMIZER_HPP
#include <utility>
#include <tuple>
#include <array>
#include <cmath>
#include <functional>
#include <limits>
#include <cassert>
namespace Slic3r { namespace opt {
// A type to hold the complete result of the optimization.
template<size_t N> struct Result {
int resultcode; // Method dependent
std::array<double, N> optimum;
double score;
};
// An interval of possible input values for optimization
class Bound {
double m_min, m_max;
public:
Bound(double min = std::numeric_limits<double>::min(),
double max = std::numeric_limits<double>::max())
: m_min(min), m_max(max)
{}
double min() const noexcept { return m_min; }
double max() const noexcept { return m_max; }
};
// Helper types for optimization function input and bounds
template<size_t N> using Input = std::array<double, N>;
template<size_t N> using Bounds = std::array<Bound, N>;
// A type for specifying the stop criteria. Setter methods can be concatenated
class StopCriteria {
// If the absolute value difference between two scores.
double m_abs_score_diff = std::nan("");
// If the relative value difference between two scores.
double m_rel_score_diff = std::nan("");
// Stop if this value or better is found.
double m_stop_score = std::nan("");
// A predicate that if evaluates to true, the optimization should terminate
// and the best result found prior to termination should be returned.
std::function<bool()> m_stop_condition = [] { return false; };
// The max allowed number of iterations.
unsigned m_max_iterations = 0;
public:
StopCriteria & abs_score_diff(double val)
{
m_abs_score_diff = val; return *this;
}
double abs_score_diff() const { return m_abs_score_diff; }
StopCriteria & rel_score_diff(double val)
{
m_rel_score_diff = val; return *this;
}
double rel_score_diff() const { return m_rel_score_diff; }
StopCriteria & stop_score(double val)
{
m_stop_score = val; return *this;
}
double stop_score() const { return m_stop_score; }
StopCriteria & max_iterations(double val)
{
m_max_iterations = val; return *this;
}
double max_iterations() const { return m_max_iterations; }
template<class Fn> StopCriteria & stop_condition(Fn &&cond)
{
m_stop_condition = cond; return *this;
}
bool stop_condition() { return m_stop_condition(); }
};
// Helper class to use optimization methods involving gradient.
template<size_t N> struct ScoreGradient {
double score;
std::optional<std::array<double, N>> gradient;
ScoreGradient(double s, const std::array<double, N> &grad)
: score{s}, gradient{grad}
{}
};
// Helper to be used in static_assert.
template<class T> struct always_false { enum { value = false }; };
// Basic interface to optimizer object
template<class Method, class Enable = void> class Optimizer {
public:
Optimizer(const StopCriteria &)
{
static_assert (always_false<Method>::value,
"Optimizer unimplemented for given method!");
}
// Switch optimization towards function minimum
Optimizer &to_min() { return *this; }
// Switch optimization towards function maximum
Optimizer &to_max() { return *this; }
// Set criteria for successive optimizations
Optimizer &set_criteria(const StopCriteria &) { return *this; }
// Get current criteria
StopCriteria get_criteria() const { return {}; };
// Find function minimum or maximum for Func which has has signature:
// double(const Input<N> &input) and input with dimension N
//
// Initial starting point can be given as the second parameter.
//
// For each dimension an interval (Bound) has to be given marking the bounds
// for that dimension.
//
// initvals have to be within the specified bounds, otherwise its undefined
// behavior.
//
// Func can return a score of type double or optionally a ScoreGradient
// class to indicate the function gradient for a optimization methods that
// make use of the gradient.
template<class Func, size_t N>
Result<N> optimize(Func&& /*func*/,
const Input<N> &/*initvals*/,
const Bounds<N>& /*bounds*/) { return {}; }
// optional for randomized methods:
void seed(long /*s*/) {}
};
namespace detail {
// Helper to convert C style array to std::array. The copy should be optimized
// away with modern compilers.
template<size_t N, class T> auto to_arr(const T *a)
{
std::array<T, N> r;
std::copy(a, a + N, std::begin(r));
return r;
}
template<size_t N, class T> auto to_arr(const T (&a) [N])
{
return to_arr<N>(static_cast<const T *>(a));
}
} // namespace detail
// Helper functions to create bounds, initial value
template<size_t N> Bounds<N> bounds(const Bound (&b) [N]) { return detail::to_arr(b); }
template<size_t N> Input<N> initvals(const double (&a) [N]) { return detail::to_arr(a); }
template<size_t N> auto score_gradient(double s, const double (&grad)[N])
{
return ScoreGradient<N>(s, detail::to_arr(grad));
}
}} // namespace Slic3r::opt
#endif // OPTIMIZER_HPP

View file

@ -1,4 +1,5 @@
#include "PlaceholderParser.hpp"
#include "Exception.hpp"
#include "Flow.hpp"
#include <cstring>
#include <ctime>
@ -1303,7 +1304,7 @@ static std::string process_macro(const std::string &templ, client::MyContext &co
if (!context.error_message.empty()) {
if (context.error_message.back() != '\n' && context.error_message.back() != '\r')
context.error_message += '\n';
throw std::runtime_error(context.error_message);
throw Slic3r::RuntimeError(context.error_message);
}
return output;
}
@ -1319,7 +1320,7 @@ std::string PlaceholderParser::process(const std::string &templ, unsigned int cu
}
// Evaluate a boolean expression using the full expressive power of the PlaceholderParser boolean expression syntax.
// Throws std::runtime_error on syntax or runtime error.
// Throws Slic3r::RuntimeError on syntax or runtime error.
bool PlaceholderParser::evaluate_boolean_expression(const std::string &templ, const DynamicConfig &config, const DynamicConfig *config_override)
{
client::MyContext context;

View file

@ -40,11 +40,11 @@ public:
const DynamicConfig* external_config() const { return m_external_config; }
// Fill in the template using a macro processing language.
// Throws std::runtime_error on syntax or runtime error.
// Throws Slic3r::RuntimeError on syntax or runtime error.
std::string process(const std::string &templ, unsigned int current_extruder_id = 0, const DynamicConfig *config_override = nullptr) const;
// Evaluate a boolean expression using the full expressive power of the PlaceholderParser boolean expression syntax.
// Throws std::runtime_error on syntax or runtime error.
// Throws Slic3r::RuntimeError on syntax or runtime error.
static bool evaluate_boolean_expression(const std::string &templ, const DynamicConfig &config, const DynamicConfig *config_override = nullptr);
// Update timestamp, year, month, day, hour, minute, second variables at the provided config.

View file

@ -44,16 +44,6 @@ Pointf3s transform(const Pointf3s& points, const Transform3d& t)
return ret_points;
}
void Point::rotate(double angle)
{
double cur_x = (double)(*this)(0);
double cur_y = (double)(*this)(1);
double s = ::sin(angle);
double c = ::cos(angle);
(*this)(0) = (coord_t)round(c * cur_x - s * cur_y);
(*this)(1) = (coord_t)round(c * cur_y + s * cur_x);
}
void Point::rotate(double angle, const Point &center)
{
double cur_x = (double)(*this)(0);

View file

@ -105,6 +105,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 Vec2d &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); }
// This method allows you to assign Eigen expressions to MyVectorType
template<typename OtherDerived>
@ -121,7 +122,14 @@ public:
Point& operator*=(const double &rhs) { (*this)(0) = coord_t((*this)(0) * rhs); (*this)(1) = coord_t((*this)(1) * rhs); return *this; }
Point operator*(const double &rhs) { return Point((*this)(0) * rhs, (*this)(1) * rhs); }
void rotate(double angle);
void rotate(double angle) { this->rotate(std::cos(angle), std::sin(angle)); }
void rotate(double cos_a, double sin_a) {
double cur_x = (double)(*this)(0);
double cur_y = (double)(*this)(1);
(*this)(0) = (coord_t)round(cos_a * cur_x - sin_a * cur_y);
(*this)(1) = (coord_t)round(cos_a * cur_y + sin_a * cur_x);
}
void rotate(double angle, const Point &center);
Point rotated(double angle) const { Point res(*this); res.rotate(angle); return res; }
Point rotated(double angle, const Point &center) const { Point res(*this); res.rotate(angle, center); return res; }

View file

@ -1,5 +1,6 @@
#include "BoundingBox.hpp"
#include "ClipperUtils.hpp"
#include "Exception.hpp"
#include "Polygon.hpp"
#include "Polyline.hpp"
@ -16,7 +17,7 @@ Polyline Polygon::split_at_vertex(const Point &point) const
for (const Point &pt : this->points)
if (pt == point)
return this->split_at_index(int(&pt - &this->points.front()));
throw std::invalid_argument("Point not found");
throw Slic3r::InvalidArgument("Point not found");
return Polyline();
}

View file

@ -88,6 +88,14 @@ inline double total_length(const Polygons &polylines) {
return total;
}
inline double area(const Polygons &polys)
{
double s = 0.;
for (auto &p : polys) s += p.area();
return s;
}
// Remove sticks (tentacles with zero area) from the polygon.
extern bool remove_sticks(Polygon &poly);
extern bool remove_sticks(Polygons &polys);

View file

@ -1,5 +1,6 @@
#include "BoundingBox.hpp"
#include "Polyline.hpp"
#include "Exception.hpp"
#include "ExPolygon.hpp"
#include "ExPolygonCollection.hpp"
#include "Line.hpp"
@ -19,7 +20,7 @@ Polyline::operator Polylines() const
Polyline::operator Line() const
{
if (this->points.size() > 2)
throw std::invalid_argument("Can't convert polyline with more than two points to a line");
throw Slic3r::InvalidArgument("Can't convert polyline with more than two points to a line");
return Line(this->points.front(), this->points.back());
}
@ -207,7 +208,7 @@ BoundingBox get_extents(const Polylines &polylines)
const Point& leftmost_point(const Polylines &polylines)
{
if (polylines.empty())
throw std::invalid_argument("leftmost_point() called on empty PolylineCollection");
throw Slic3r::InvalidArgument("leftmost_point() called on empty PolylineCollection");
Polylines::const_iterator it = polylines.begin();
const Point *p = &it->leftmost_point();
for (++ it; it != polylines.end(); ++it) {

View file

@ -1,5 +1,6 @@
#include <cassert>
#include "Exception.hpp"
#include "Preset.hpp"
#include "AppConfig.hpp"
@ -107,7 +108,7 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem
const std::string id = path.stem().string();
if (! boost::filesystem::exists(path)) {
throw std::runtime_error((boost::format("Cannot load Vendor Config Bundle `%1%`: File not found: `%2%`.") % id % path).str());
throw Slic3r::RuntimeError((boost::format("Cannot load Vendor Config Bundle `%1%`: File not found: `%2%`.") % id % path).str());
}
VendorProfile res(id);
@ -117,7 +118,7 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem
{
auto res = tree.find(key);
if (res == tree.not_found()) {
throw std::runtime_error((boost::format("Vendor Config Bundle `%1%` is not valid: Missing secion or key: `%2%`.") % id % key).str());
throw Slic3r::RuntimeError((boost::format("Vendor Config Bundle `%1%` is not valid: Missing secion or key: `%2%`.") % id % key).str());
}
return res;
};
@ -129,7 +130,7 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem
auto config_version_str = get_or_throw(vendor_section, "config_version")->second.data();
auto config_version = Semver::parse(config_version_str);
if (! config_version) {
throw std::runtime_error((boost::format("Vendor Config Bundle `%1%` is not valid: Cannot parse config_version: `%2%`.") % id % config_version_str).str());
throw Slic3r::RuntimeError((boost::format("Vendor Config Bundle `%1%` is not valid: Cannot parse config_version: `%2%`.") % id % config_version_str).str());
} else {
res.config_version = std::move(*config_version);
}
@ -672,9 +673,9 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri
preset.file << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed";
preset.loaded = true;
} catch (const std::ifstream::failure &err) {
throw std::runtime_error(std::string("The selected preset cannot be loaded: ") + preset.file + "\n\tReason: " + err.what());
throw Slic3r::RuntimeError(std::string("The selected preset cannot be loaded: ") + preset.file + "\n\tReason: " + err.what());
} catch (const std::runtime_error &err) {
throw std::runtime_error(std::string("Failed loading the preset file: ") + preset.file + "\n\tReason: " + err.what());
throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + preset.file + "\n\tReason: " + err.what());
}
presets_loaded.emplace_back(preset);
} catch (const std::runtime_error &err) {
@ -686,7 +687,7 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri
std::sort(m_presets.begin() + m_num_default_presets, m_presets.end());
this->select_preset(first_visible_idx());
if (! errors_cummulative.empty())
throw std::runtime_error(errors_cummulative);
throw Slic3r::RuntimeError(errors_cummulative);
}
// Load a preset from an already parsed config file, insert it into the sorted sequence of presets
@ -1365,9 +1366,10 @@ const std::vector<std::string>& PhysicalPrinter::printer_options()
"print_host",
"printhost_apikey",
"printhost_cafile",
"authorization_type",
"login",
"password"
"printhost_authorization_type",
// HTTP digest authentization (RFC 2617)
"printhost_user",
"printhost_password"
};
}
return s_opts;
@ -1412,11 +1414,11 @@ const std::set<std::string>& PhysicalPrinter::get_preset_names() const
bool PhysicalPrinter::has_empty_config() const
{
return config.opt_string("print_host" ).empty() &&
config.opt_string("printhost_apikey").empty() &&
config.opt_string("printhost_cafile").empty() &&
config.opt_string("login" ).empty() &&
config.opt_string("password" ).empty();
return config.opt_string("print_host" ).empty() &&
config.opt_string("printhost_apikey" ).empty() &&
config.opt_string("printhost_cafile" ).empty() &&
config.opt_string("printhost_user" ).empty() &&
config.opt_string("printhost_password").empty();
}
void PhysicalPrinter::update_preset_names_in_config()
@ -1441,7 +1443,7 @@ void PhysicalPrinter::save(const std::string& file_name_from, const std::string&
void PhysicalPrinter::update_from_preset(const Preset& preset)
{
config.apply_only(preset.config, printer_options(), false);
config.apply_only(preset.config, printer_options(), true);
// add preset names to the options list
auto ret = preset_names.emplace(preset.name);
update_preset_names_in_config();
@ -1476,8 +1478,8 @@ bool PhysicalPrinter::delete_preset(const std::string& preset_name)
return preset_names.erase(preset_name) > 0;
}
PhysicalPrinter::PhysicalPrinter(const std::string& name, const Preset& preset) :
name(name)
PhysicalPrinter::PhysicalPrinter(const std::string& name, const DynamicPrintConfig &default_config, const Preset& preset) :
name(name), config(default_config)
{
update_from_preset(preset);
}
@ -1514,6 +1516,13 @@ std::string PhysicalPrinter::get_preset_name(std::string name)
PhysicalPrinterCollection::PhysicalPrinterCollection( const std::vector<std::string>& keys)
{
// Default config for a physical printer containing all key/value pairs of PhysicalPrinter::printer_options().
for (const std::string &key : keys) {
const ConfigOptionDef *opt = print_config_def.get(key);
assert(opt);
assert(opt->default_value);
m_default_config.set_key_value(key, opt->default_value->clone());
}
}
// Load all printers found in dir_path.
@ -1539,7 +1548,7 @@ void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const
continue;
}
try {
PhysicalPrinter printer(name);
PhysicalPrinter printer(name, this->default_config());
printer.file = dir_entry.path().string();
// Load the preset file, apply preset values on top of defaults.
try {
@ -1549,10 +1558,10 @@ void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const
printer.loaded = true;
}
catch (const std::ifstream::failure& err) {
throw std::runtime_error(std::string("The selected preset cannot be loaded: ") + printer.file + "\n\tReason: " + err.what());
throw Slic3r::RuntimeError(std::string("The selected preset cannot be loaded: ") + printer.file + "\n\tReason: " + err.what());
}
catch (const std::runtime_error& err) {
throw std::runtime_error(std::string("Failed loading the preset file: ") + printer.file + "\n\tReason: " + err.what());
throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + printer.file + "\n\tReason: " + err.what());
}
printers_loaded.emplace_back(printer);
}
@ -1564,7 +1573,7 @@ void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const
m_printers.insert(m_printers.end(), std::make_move_iterator(printers_loaded.begin()), std::make_move_iterator(printers_loaded.end()));
std::sort(m_printers.begin(), m_printers.end());
if (!errors_cummulative.empty())
throw std::runtime_error(errors_cummulative);
throw Slic3r::RuntimeError(errors_cummulative);
}
// if there is saved user presets, contains information about "Print Host upload",
@ -1590,7 +1599,7 @@ void PhysicalPrinterCollection::load_printers_from_presets(PrinterPresetCollecti
new_printer_name = (boost::format("Printer %1%") % ++cnt).str();
// create new printer from this preset
PhysicalPrinter printer(new_printer_name, preset);
PhysicalPrinter printer(new_printer_name, this->default_config(), preset);
printer.loaded = true;
save_printer(printer);
}

View file

@ -460,8 +460,7 @@ private:
// If a preset does not exist, an iterator is returned indicating where to insert a preset with the same name.
std::deque<Preset>::iterator find_preset_internal(const std::string &name)
{
Preset key(m_type, name);
auto it = std::lower_bound(m_presets.begin() + m_num_default_presets, m_presets.end(), key);
auto it = Slic3r::lower_bound_by_predicate(m_presets.begin() + m_num_default_presets, m_presets.end(), [&name](const auto& l) { return l.name < name; });
if (it == m_presets.end() || it->name != name) {
// Preset has not been not found in the sorted list of non-default presets. Try the defaults.
for (size_t i = 0; i < m_num_default_presets; ++ i)
@ -539,9 +538,8 @@ namespace PresetUtils {
class PhysicalPrinter
{
public:
PhysicalPrinter() {}
PhysicalPrinter(const std::string& name) : name(name){}
PhysicalPrinter(const std::string& name, const Preset& preset);
PhysicalPrinter(const std::string& name, const DynamicPrintConfig &default_config) : name(name), config(default_config) {}
PhysicalPrinter(const std::string& name, const DynamicPrintConfig &default_config, const Preset& preset);
void set_name(const std::string &name);
// Name of the Physical Printer, usually derived form the file name.
@ -698,6 +696,8 @@ public:
// Generate a file path from a profile name. Add the ".ini" suffix if it is missing.
std::string path_from_name(const std::string& new_name) const;
const DynamicPrintConfig& default_config() const { return m_default_config; }
private:
PhysicalPrinterCollection& operator=(const PhysicalPrinterCollection& other);
@ -707,9 +707,7 @@ private:
// If a preset does not exist, an iterator is returned indicating where to insert a preset with the same name.
std::deque<PhysicalPrinter>::iterator find_printer_internal(const std::string& name)
{
PhysicalPrinter printer(name);
auto it = std::lower_bound(m_printers.begin(), m_printers.end(), printer);
return it;
return Slic3r::lower_bound_by_predicate(m_printers.begin(), m_printers.end(), [&name](const auto& l) { return l.name < name; });
}
std::deque<PhysicalPrinter>::const_iterator find_printer_internal(const std::string& name) const
{
@ -723,6 +721,9 @@ private:
// so that the addresses of the presets don't change during resizing of the container.
std::deque<PhysicalPrinter> m_printers;
// Default config for a physical printer containing all key/value pairs of PhysicalPrinter::printer_options().
DynamicPrintConfig m_default_config;
// Selected printer.
size_t m_idx_selected = size_t(-1);
// The name of the preset which is currently select for this printer

View file

@ -157,7 +157,7 @@ void PresetBundle::setup_directories()
subdir.make_preferred();
if (! boost::filesystem::is_directory(subdir) &&
! boost::filesystem::create_directory(subdir))
throw std::runtime_error(std::string("Slic3r was unable to create its data directory at ") + subdir.string());
throw Slic3r::RuntimeError(std::string("Slic3r was unable to create its data directory at ") + subdir.string());
}
}
@ -207,7 +207,7 @@ void PresetBundle::load_presets(AppConfig &config, const std::string &preferred_
this->update_multi_material_filament_presets();
this->update_compatible(PresetSelectCompatibleType::Never);
if (! errors_cummulative.empty())
throw std::runtime_error(errors_cummulative);
throw Slic3r::RuntimeError(errors_cummulative);
this->load_selections(config, preferred_model_id);
}
@ -327,6 +327,32 @@ const std::string& PresetBundle::get_preset_name_by_alias( const Preset::Type& p
return presets.get_preset_name_by_alias(alias);
}
void PresetBundle::save_changes_for_preset(const std::string& new_name, Preset::Type type,
const std::vector<std::string>& unselected_options)
{
PresetCollection& presets = type == Preset::TYPE_PRINT ? prints :
type == Preset::TYPE_SLA_PRINT ? sla_prints :
type == Preset::TYPE_FILAMENT ? filaments :
type == Preset::TYPE_SLA_MATERIAL ? sla_materials : printers;
// if we want to save just some from selected options
if (!unselected_options.empty()) {
// revert unselected options to the old values
presets.get_edited_preset().config.apply_only(presets.get_selected_preset().config, unselected_options);
}
// Save the preset into Slic3r::data_dir / presets / section_name / preset_name.ini
presets.save_current_preset(new_name);
// Mark the print & filament enabled if they are compatible with the currently selected preset.
// If saving the preset changes compatibility with other presets, keep the now incompatible dependent presets selected, however with a "red flag" icon showing that they are no more compatible.
update_compatible(PresetSelectCompatibleType::Never);
if (type == Preset::TYPE_FILAMENT) {
// synchronize the first filament presets.
set_filament_preset(0, filaments.get_selected_preset_name());
}
}
void PresetBundle::load_installed_filaments(AppConfig &config)
{
if (! config.has_section(AppConfig::SECTION_FILAMENTS)) {
@ -653,21 +679,21 @@ void PresetBundle::load_config_file(const std::string &path)
boost::nowide::ifstream ifs(path);
boost::property_tree::read_ini(ifs, tree);
} catch (const std::ifstream::failure &err) {
throw std::runtime_error(std::string("The Config Bundle cannot be loaded: ") + path + "\n\tReason: " + err.what());
throw Slic3r::RuntimeError(std::string("The Config Bundle cannot be loaded: ") + path + "\n\tReason: " + err.what());
} catch (const boost::property_tree::file_parser_error &err) {
throw std::runtime_error((boost::format("Failed loading the Config Bundle \"%1%\": %2% at line %3%")
throw Slic3r::RuntimeError((boost::format("Failed loading the Config Bundle \"%1%\": %2% at line %3%")
% err.filename() % err.message() % err.line()).str());
} catch (const std::runtime_error &err) {
throw std::runtime_error(std::string("Failed loading the preset file: ") + path + "\n\tReason: " + err.what());
throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + path + "\n\tReason: " + err.what());
}
// 2) Continue based on the type of the configuration file.
ConfigFileType config_file_type = guess_config_file_type(tree);
switch (config_file_type) {
case CONFIG_FILE_TYPE_UNKNOWN:
throw std::runtime_error(std::string("Unknown configuration file type: ") + path);
throw Slic3r::RuntimeError(std::string("Unknown configuration file type: ") + path);
case CONFIG_FILE_TYPE_APP_CONFIG:
throw std::runtime_error(std::string("Invalid configuration file: ") + path + ". This is an application config file.");
throw Slic3r::RuntimeError(std::string("Invalid configuration file: ") + path + ". This is an application config file.");
case CONFIG_FILE_TYPE_CONFIG:
{
// Initialize a config from full defaults.

View file

@ -130,6 +130,10 @@ public:
const std::string& get_preset_name_by_alias(const Preset::Type& preset_type, const std::string& alias) const;
// Save current preset of a required type under a new name. If the name is different from the old one,
// Unselected option would be reverted to the beginning values
void save_changes_for_preset(const std::string& new_name, Preset::Type type, const std::vector<std::string>& unselected_options);
static const char *PRUSA_BUNDLE;
private:
std::string load_system_presets();

View file

@ -1,5 +1,6 @@
#include "clipper/clipper_z.hpp"
#include "Exception.hpp"
#include "Print.hpp"
#include "BoundingBox.hpp"
#include "ClipperUtils.hpp"
@ -1507,7 +1508,7 @@ BoundingBox Print::total_bounding_box() const
double Print::skirt_first_layer_height() const
{
if (m_objects.empty())
throw std::invalid_argument("skirt_first_layer_height() can't be called without PrintObjects");
throw Slic3r::InvalidArgument("skirt_first_layer_height() can't be called without PrintObjects");
return m_objects.front()->config().get_abs_value("first_layer_height");
}
@ -1583,7 +1584,7 @@ void Print::auto_assign_extruders(ModelObject* model_object) const
// Slicing process, running at a background thread.
void Print::process()
{
BOOST_LOG_TRIVIAL(info) << "Staring the slicing process." << log_memory_info();
BOOST_LOG_TRIVIAL(info) << "Starting the slicing process." << log_memory_info();
for (PrintObject *obj : m_objects)
obj->make_perimeters();
this->set_status(70, L("Infilling layers"));
@ -1603,7 +1604,7 @@ void Print::process()
// Initialize the tool ordering, so it could be used by the G-code preview slider for planning tool changes and filament switches.
m_tool_ordering = ToolOrdering(*this, -1, false);
if (m_tool_ordering.empty() || m_tool_ordering.last_extruder() == unsigned(-1))
throw std::runtime_error("The print is empty. The model is not printable with current print settings.");
throw Slic3r::SlicingError("The print is empty. The model is not printable with current print settings.");
}
this->set_done(psWipeTower);
}

View file

@ -30,6 +30,12 @@ enum class SlicingMode : uint32_t;
class Layer;
class SupportLayer;
namespace FillAdaptive {
struct Octree;
struct OctreeDeleter;
using OctreePtr = std::unique_ptr<Octree, OctreeDeleter>;
};
// Print step IDs for keeping track of the print state.
enum PrintStep {
psSkirt,
@ -235,6 +241,7 @@ private:
void discover_horizontal_shells();
void combine_infill();
void _generate_support_material();
std::pair<FillAdaptive::OctreePtr, FillAdaptive::OctreePtr> prepare_adaptive_infill_data();
// XYZ in scaled coordinates
Vec3crd m_size;

View file

@ -1,3 +1,4 @@
#include "Exception.hpp"
#include "PrintBase.hpp"
#include <boost/filesystem.hpp>
@ -68,7 +69,7 @@ std::string PrintBase::output_filename(const std::string &format, const std::str
filename = boost::filesystem::change_extension(filename, default_ext);
return filename.string();
} catch (std::runtime_error &err) {
throw std::runtime_error(L("Failed processing of the output_filename_format template.") + "\n" + err.what());
throw Slic3r::RuntimeError(L("Failed processing of the output_filename_format template.") + "\n" + err.what());
}
}

View file

@ -133,13 +133,13 @@ void PrintConfigDef::init_common_params()
// Options used by physical printers
def = this->add("login", coString);
def->label = L("Login");
def = this->add("printhost_user", coString);
def->label = L("User");
// def->tooltip = L("");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionString(""));
def = this->add("password", coString);
def = this->add("printhost_password", coString);
def->label = L("Password");
// def->tooltip = L("");
def->mode = comAdvanced;
@ -151,7 +151,7 @@ void PrintConfigDef::init_common_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionString(""));
def = this->add("authorization_type", coEnum);
def = this->add("printhost_authorization_type", coEnum);
def->label = L("Authorization Type");
// def->tooltip = L("");
def->enum_keys_map = &ConfigOptionEnum<AuthorizationType>::get_enum_values();
@ -881,6 +881,8 @@ void PrintConfigDef::init_fff_params()
def->enum_values.push_back("hilbertcurve");
def->enum_values.push_back("archimedeanchords");
def->enum_values.push_back("octagramspiral");
def->enum_values.push_back("adaptivecubic");
def->enum_values.push_back("supportcubic");
def->enum_labels.push_back(L("Rectilinear"));
def->enum_labels.push_back(L("Grid"));
def->enum_labels.push_back(L("Triangles"));
@ -894,6 +896,8 @@ void PrintConfigDef::init_fff_params()
def->enum_labels.push_back(L("Hilbert Curve"));
def->enum_labels.push_back(L("Archimedean Chords"));
def->enum_labels.push_back(L("Octagram Spiral"));
def->enum_labels.push_back(L("Adaptive Cubic"));
def->enum_labels.push_back(L("Support Cubic"));
def->set_default_value(new ConfigOptionEnum<InfillPattern>(ipStars));
def = this->add("first_layer_acceleration", coFloat);

View file

@ -39,7 +39,7 @@ enum AuthorizationType {
enum InfillPattern : int {
ipRectilinear, ipMonotonous, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb,
ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipCount,
ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipSupportCubic, ipCount,
};
enum class IroningType {
@ -139,6 +139,8 @@ template<> inline const t_config_enum_values& ConfigOptionEnum<InfillPattern>::g
keys_map["hilbertcurve"] = ipHilbertCurve;
keys_map["archimedeanchords"] = ipArchimedeanChords;
keys_map["octagramspiral"] = ipOctagramSpiral;
keys_map["adaptivecubic"] = ipAdaptiveCubic;
keys_map["supportcubic"] = ipSupportCubic;
}
return keys_map;
}

View file

@ -1,3 +1,4 @@
#include "Exception.hpp"
#include "Print.hpp"
#include "BoundingBox.hpp"
#include "ClipperUtils.hpp"
@ -9,6 +10,9 @@
#include "Surface.hpp"
#include "Slicing.hpp"
#include "Utils.hpp"
#include "AABBTreeIndirect.hpp"
#include "Fill/FillAdaptive.hpp"
#include "Format/STL.hpp"
#include <utility>
#include <boost/log/trivial.hpp>
@ -135,7 +139,7 @@ void PrintObject::slice()
}
});
if (m_layers.empty())
throw std::runtime_error("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n");
throw Slic3r::SlicingError("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n");
this->set_done(posSlice);
}
@ -369,13 +373,15 @@ void PrintObject::infill()
this->prepare_infill();
if (this->set_started(posInfill)) {
auto [adaptive_fill_octree, support_fill_octree] = this->prepare_adaptive_infill_data();
BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start";
tbb::parallel_for(
tbb::blocked_range<size_t>(0, m_layers.size()),
[this](const tbb::blocked_range<size_t>& range) {
[this, &adaptive_fill_octree = adaptive_fill_octree, &support_fill_octree = support_fill_octree](const tbb::blocked_range<size_t>& range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
m_print->throw_if_canceled();
m_layers[layer_idx]->make_fills();
m_layers[layer_idx]->make_fills(adaptive_fill_octree.get(), support_fill_octree.get());
}
}
);
@ -421,13 +427,31 @@ void PrintObject::generate_support_material()
// therefore they cannot be printed without supports.
for (const Layer *layer : m_layers)
if (layer->empty())
throw std::runtime_error("Levitating objects cannot be printed without supports.");
throw Slic3r::SlicingError("Levitating objects cannot be printed without supports.");
#endif
}
this->set_done(posSupportMaterial);
}
}
std::pair<FillAdaptive::OctreePtr, FillAdaptive::OctreePtr> PrintObject::prepare_adaptive_infill_data()
{
using namespace FillAdaptive;
auto [adaptive_line_spacing, support_line_spacing] = adaptive_fill_line_spacing(*this);
if (adaptive_line_spacing == 0. && support_line_spacing == 0.)
return std::make_pair(OctreePtr(), OctreePtr());
indexed_triangle_set mesh = this->model_object()->raw_indexed_triangle_set();
// Rotate mesh and build octree on it with axis-aligned (standart base) cubes.
Transform3d m = m_trafo;
m.translate(Vec3d(- unscale<float>(m_center_offset.x()), - unscale<float>(m_center_offset.y()), 0));
its_transform(mesh, transform_to_octree().toRotationMatrix() * m, true);
return std::make_pair(
adaptive_line_spacing ? build_octree(mesh, adaptive_line_spacing, false) : OctreePtr(),
support_line_spacing ? build_octree(mesh, support_line_spacing, true) : OctreePtr());
}
void PrintObject::clear_layers()
{
for (Layer *l : m_layers)

View file

@ -1,3 +1,4 @@
#include "Exception.hpp"
#include "Print.hpp"
namespace Slic3r {
@ -13,7 +14,7 @@ unsigned int PrintRegion::extruder(FlowRole role) const
else if (role == frSolidInfill || role == frTopSolidInfill)
extruder = m_config.solid_infill_extruder;
else
throw std::invalid_argument("Unknown role");
throw Slic3r::InvalidArgument("Unknown role");
return extruder;
}
@ -40,7 +41,7 @@ Flow PrintRegion::flow(FlowRole role, double layer_height, bool bridge, bool fir
} else if (role == frTopSolidInfill) {
config_width = m_config.top_infill_extrusion_width;
} else {
throw std::invalid_argument("Unknown role");
throw Slic3r::InvalidArgument("Unknown role");
}
}

View file

@ -4,7 +4,11 @@
#include <tbb/spin_mutex.h>
#include <tbb/mutex.h>
#include <tbb/parallel_for.h>
#include <tbb/parallel_reduce.h>
#include <algorithm>
#include <numeric>
#include <libslic3r/libslic3r.h>
namespace Slic3r {
@ -21,28 +25,56 @@ template<> struct _ccr<true>
using SpinningMutex = tbb::spin_mutex;
using BlockingMutex = tbb::mutex;
template<class Fn, class It>
static IteratorOnly<It, void> loop_(const tbb::blocked_range<It> &range, Fn &&fn)
{
for (auto &el : range) fn(el);
}
template<class Fn, class I>
static IntegerOnly<I, void> loop_(const tbb::blocked_range<I> &range, Fn &&fn)
{
for (I i = range.begin(); i < range.end(); ++i) fn(i);
}
template<class It, class Fn>
static IteratorOnly<It, void> for_each(It from,
It to,
Fn && fn,
size_t granularity = 1)
static void for_each(It from, It to, Fn &&fn, size_t granularity = 1)
{
tbb::parallel_for(tbb::blocked_range{from, to, granularity},
[&fn, from](const auto &range) {
for (auto &el : range) fn(el);
loop_(range, std::forward<Fn>(fn));
});
}
template<class I, class Fn>
static IntegerOnly<I, void> for_each(I from,
I to,
Fn && fn,
size_t granularity = 1)
template<class I, class MergeFn, class T, class AccessFn>
static T reduce(I from,
I to,
const T &init,
MergeFn &&mergefn,
AccessFn &&access,
size_t granularity = 1
)
{
tbb::parallel_for(tbb::blocked_range{from, to, granularity},
[&fn](const auto &range) {
for (I i = range.begin(); i < range.end(); ++i) fn(i);
});
return tbb::parallel_reduce(
tbb::blocked_range{from, to, granularity}, init,
[&](const auto &range, T subinit) {
T acc = subinit;
loop_(range, [&](auto &i) { acc = mergefn(acc, access(i)); });
return acc;
},
std::forward<MergeFn>(mergefn));
}
template<class I, class MergeFn, class T>
static IteratorOnly<I, T> reduce(I from,
I to,
const T & init,
MergeFn &&mergefn,
size_t granularity = 1)
{
return reduce(
from, to, init, std::forward<MergeFn>(mergefn),
[](typename I::value_type &i) { return i; }, granularity);
}
};
@ -55,23 +87,52 @@ public:
using SpinningMutex = _Mtx;
using BlockingMutex = _Mtx;
template<class It, class Fn>
static IteratorOnly<It, void> for_each(It from,
It to,
Fn &&fn,
size_t /* ignore granularity */ = 1)
template<class Fn, class It>
static IteratorOnly<It, void> loop_(It from, It to, Fn &&fn)
{
for (auto it = from; it != to; ++it) fn(*it);
}
template<class I, class Fn>
static IntegerOnly<I, void> for_each(I from,
I to,
Fn &&fn,
size_t /* ignore granularity */ = 1)
template<class Fn, class I>
static IntegerOnly<I, void> loop_(I from, I to, Fn &&fn)
{
for (I i = from; i < to; ++i) fn(i);
}
template<class It, class Fn>
static void for_each(It from,
It to,
Fn &&fn,
size_t /* ignore granularity */ = 1)
{
loop_(from, to, std::forward<Fn>(fn));
}
template<class I, class MergeFn, class T, class AccessFn>
static T reduce(I from,
I to,
const T & init,
MergeFn &&mergefn,
AccessFn &&access,
size_t /*granularity*/ = 1
)
{
T acc = init;
loop_(from, to, [&](auto &i) { acc = mergefn(acc, access(i)); });
return acc;
}
template<class I, class MergeFn, class T>
static IteratorOnly<I, T> reduce(I from,
I to,
const T &init,
MergeFn &&mergefn,
size_t /*granularity*/ = 1
)
{
return reduce(from, to, init, std::forward<MergeFn>(mergefn),
[](typename I::value_type &i) { return i; });
}
};
using ccr = _ccr<USE_FULL_CONCURRENCY>;

View file

@ -1,35 +1,259 @@
#include <limits>
#include <exception>
#include <libnest2d/optimizers/nlopt/genetic.hpp>
#include <libslic3r/SLA/Rotfinder.hpp>
#include <libslic3r/SLA/SupportTree.hpp>
#include <libslic3r/SLA/Concurrency.hpp>
#include <libslic3r/Optimize/BruteforceOptimizer.hpp>
#include "libslic3r/SLAPrint.hpp"
#include "libslic3r/PrintConfig.hpp"
#include <libslic3r/Geometry.hpp>
#include "Model.hpp"
namespace Slic3r {
namespace sla {
#include <thread>
std::array<double, 3> find_best_rotation(const ModelObject& modelobj,
float accuracy,
std::function<void(unsigned)> statuscb,
std::function<bool()> stopcond)
namespace Slic3r { namespace sla {
inline bool is_on_floor(const SLAPrintObject &mo)
{
using libnest2d::opt::Method;
using libnest2d::opt::bound;
using libnest2d::opt::Optimizer;
using libnest2d::opt::TOptimizer;
using libnest2d::opt::StopCriteria;
auto opt_elevation = mo.config().support_object_elevation.getFloat();
auto opt_padaround = mo.config().pad_around_object.getBool();
static const unsigned MAX_TRIES = 100000;
return opt_elevation < EPSILON || opt_padaround;
}
// Find transformed mesh ground level without copy and with parallel reduce.
double find_ground_level(const TriangleMesh &mesh,
const Transform3d & tr,
size_t threads)
{
size_t vsize = mesh.its.vertices.size();
auto minfn = [](double a, double b) { return std::min(a, b); };
auto accessfn = [&mesh, &tr] (size_t vi) {
return (tr * mesh.its.vertices[vi].template cast<double>()).z();
};
double zmin = std::numeric_limits<double>::max();
size_t granularity = vsize / threads;
return ccr_par::reduce(size_t(0), vsize, zmin, minfn, accessfn, granularity);
}
// Get the vertices of a triangle directly in an array of 3 points
std::array<Vec3d, 3> get_triangle_vertices(const TriangleMesh &mesh,
size_t faceidx)
{
const auto &face = mesh.its.indices[faceidx];
return {Vec3d{mesh.its.vertices[face(0)].cast<double>()},
Vec3d{mesh.its.vertices[face(1)].cast<double>()},
Vec3d{mesh.its.vertices[face(2)].cast<double>()}};
}
std::array<Vec3d, 3> get_transformed_triangle(const TriangleMesh &mesh,
const Transform3d & tr,
size_t faceidx)
{
const auto &tri = get_triangle_vertices(mesh, faceidx);
return {tr * tri[0], tr * tri[1], tr * tri[2]};
}
// Get area and normal of a triangle
struct Facestats {
Vec3d normal;
double area;
explicit Facestats(const std::array<Vec3d, 3> &triangle)
{
Vec3d U = triangle[1] - triangle[0];
Vec3d V = triangle[2] - triangle[0];
Vec3d C = U.cross(V);
normal = C.normalized();
area = 0.5 * C.norm();
}
};
inline const Vec3d DOWN = {0., 0., -1.};
constexpr double POINTS_PER_UNIT_AREA = 1.;
// The score function for a particular face
inline double get_score(const Facestats &fc)
{
// Simply get the angle (acos of dot product) between the face normal and
// the DOWN vector.
double phi = 1. - std::acos(fc.normal.dot(DOWN)) / PI;
// Only consider faces that have have slopes below 90 deg:
phi = phi * (phi > 0.5);
// Make the huge slopes more significant than the smaller slopes
phi = phi * phi * phi;
// Multiply with the area of the current face
return fc.area * POINTS_PER_UNIT_AREA * phi;
}
template<class AccessFn>
double sum_score(AccessFn &&accessfn, size_t facecount, size_t Nthreads)
{
double initv = 0.;
auto mergefn = std::plus<double>{};
size_t grainsize = facecount / Nthreads;
size_t from = 0, to = facecount;
return ccr_par::reduce(from, to, initv, mergefn, accessfn, grainsize);
}
// Try to guess the number of support points needed to support a mesh
double get_model_supportedness(const TriangleMesh &mesh, const Transform3d &tr)
{
if (mesh.its.vertices.empty()) return std::nan("");
auto accessfn = [&mesh, &tr](size_t fi) {
Facestats fc{get_transformed_triangle(mesh, tr, fi)};
return get_score(fc);
};
size_t facecount = mesh.its.indices.size();
size_t Nthreads = std::thread::hardware_concurrency();
return sum_score(accessfn, facecount, Nthreads) / facecount;
}
double get_model_supportedness_onfloor(const TriangleMesh &mesh,
const Transform3d & tr)
{
if (mesh.its.vertices.empty()) return std::nan("");
size_t Nthreads = std::thread::hardware_concurrency();
double zmin = find_ground_level(mesh, tr, Nthreads);
double zlvl = zmin + 0.1; // Set up a slight tolerance from z level
auto accessfn = [&mesh, &tr, zlvl](size_t fi) {
std::array<Vec3d, 3> tri = get_transformed_triangle(mesh, tr, fi);
Facestats fc{tri};
if (tri[0].z() <= zlvl && tri[1].z() <= zlvl && tri[2].z() <= zlvl)
return -fc.area * POINTS_PER_UNIT_AREA;
return get_score(fc);
};
size_t facecount = mesh.its.indices.size();
return sum_score(accessfn, facecount, Nthreads) / facecount;
}
using XYRotation = std::array<double, 2>;
// prepare the rotation transformation
Transform3d to_transform3d(const XYRotation &rot)
{
Transform3d rt = Transform3d::Identity();
rt.rotate(Eigen::AngleAxisd(rot[1], Vec3d::UnitY()));
rt.rotate(Eigen::AngleAxisd(rot[0], Vec3d::UnitX()));
return rt;
}
XYRotation from_transform3d(const Transform3d &tr)
{
Vec3d rot3d = Geometry::Transformation {tr}.get_rotation();
return {rot3d.x(), rot3d.y()};
}
// Find the best score from a set of function inputs. Evaluate for every point.
template<size_t N, class Fn, class It, class StopCond>
std::array<double, N> find_min_score(Fn &&fn, It from, It to, StopCond &&stopfn)
{
std::array<double, N> ret;
double score = std::numeric_limits<double>::max();
size_t Nthreads = std::thread::hardware_concurrency();
size_t dist = std::distance(from, to);
std::vector<double> scores(dist, score);
ccr_par::for_each(size_t(0), dist, [&stopfn, &scores, &fn, &from](size_t i) {
if (stopfn()) return;
scores[i] = fn(*(from + i));
}, dist / Nthreads);
auto it = std::min_element(scores.begin(), scores.end());
if (it != scores.end()) ret = *(from + std::distance(scores.begin(), it));
return ret;
}
// collect the rotations for each face of the convex hull
std::vector<XYRotation> get_chull_rotations(const TriangleMesh &mesh, size_t max_count)
{
TriangleMesh chull = mesh.convex_hull_3d();
chull.require_shared_vertices();
double chull2d_area = chull.convex_hull().area();
double area_threshold = chull2d_area / (scaled<double>(1e3) * scaled(1.));
size_t facecount = chull.its.indices.size();
struct RotArea { XYRotation rot; double area; };
auto inputs = reserve_vector<RotArea>(facecount);
auto rotcmp = [](const RotArea &r1, const RotArea &r2) {
double xdiff = r1.rot[X] - r2.rot[X], ydiff = r1.rot[Y] - r2.rot[Y];
return std::abs(xdiff) < EPSILON ? ydiff < 0. : xdiff < 0.;
};
auto eqcmp = [](const XYRotation &r1, const XYRotation &r2) {
double xdiff = r1[X] - r2[X], ydiff = r1[Y] - r2[Y];
return std::abs(xdiff) < EPSILON && std::abs(ydiff) < EPSILON;
};
for (size_t fi = 0; fi < facecount; ++fi) {
Facestats fc{get_triangle_vertices(chull, fi)};
if (fc.area > area_threshold) {
auto q = Eigen::Quaterniond{}.FromTwoVectors(fc.normal, DOWN);
XYRotation rot = from_transform3d(Transform3d::Identity() * q);
RotArea ra = {rot, fc.area};
auto it = std::lower_bound(inputs.begin(), inputs.end(), ra, rotcmp);
if (it == inputs.end() || !eqcmp(it->rot, rot))
inputs.insert(it, ra);
}
}
inputs.shrink_to_fit();
if (!max_count) max_count = inputs.size();
std::sort(inputs.begin(), inputs.end(),
[](const RotArea &ra, const RotArea &rb) {
return ra.area > rb.area;
});
auto ret = reserve_vector<XYRotation>(std::min(max_count, inputs.size()));
for (const RotArea &ra : inputs) ret.emplace_back(ra.rot);
return ret;
}
Vec2d find_best_rotation(const SLAPrintObject & po,
float accuracy,
std::function<void(unsigned)> statuscb,
std::function<bool()> stopcond)
{
static const unsigned MAX_TRIES = 1000;
// return value
std::array<double, 3> rot;
XYRotation rot;
// We will use only one instance of this converted mesh to examine different
// rotations
const TriangleMesh& mesh = modelobj.raw_mesh();
TriangleMesh mesh = po.model_object()->raw_mesh();
mesh.require_shared_vertices();
// For current iteration number
// To keep track of the number of iterations
unsigned status = 0;
// The maximum number of iterations
@ -38,77 +262,61 @@ std::array<double, 3> find_best_rotation(const ModelObject& modelobj,
// call status callback with zero, because we are at the start
statuscb(status);
// So this is the object function which is called by the solver many times
// It has to yield a single value representing the current score. We will
// call the status callback in each iteration but the actual value may be
// the same for subsequent iterations (status goes from 0 to 100 but
// iterations can be many more)
auto objfunc = [&mesh, &status, &statuscb, &stopcond, max_tries]
(double rx, double ry, double rz)
{
const TriangleMesh& m = mesh;
// prepare the rotation transformation
Transform3d rt = Transform3d::Identity();
rt.rotate(Eigen::AngleAxisd(rz, Vec3d::UnitZ()));
rt.rotate(Eigen::AngleAxisd(ry, Vec3d::UnitY()));
rt.rotate(Eigen::AngleAxisd(rx, Vec3d::UnitX()));
double score = 0;
// For all triangles we calculate the normal and sum up the dot product
// (a scalar indicating how much are two vectors aligned) with each axis
// this will result in a value that is greater if a normal is aligned
// with all axes. If the normal is aligned than the triangle itself is
// orthogonal to the axes and that is good for print quality.
// TODO: some applications optimize for minimum z-axis cross section
// area. The current function is only an example of how to optimize.
// Later we can add more criteria like the number of overhangs, etc...
for(size_t i = 0; i < m.stl.facet_start.size(); i++) {
Vec3d n = m.stl.facet_start[i].normal.cast<double>();
// rotate the normal with the current rotation given by the solver
n = rt * n;
// We should score against the alignment with the reference planes
score += std::abs(n.dot(Vec3d::UnitX()));
score += std::abs(n.dot(Vec3d::UnitY()));
score += std::abs(n.dot(Vec3d::UnitZ()));
}
auto statusfn = [&statuscb, &status, &max_tries] {
// report status
if(!stopcond()) statuscb( unsigned(++status * 100.0/max_tries) );
return score;
statuscb(unsigned(++status * 100.0/max_tries) );
};
// Firing up the genetic optimizer. For now it uses the nlopt library.
StopCriteria stc;
stc.max_iterations = max_tries;
stc.relative_score_difference = 1e-3;
stc.stop_condition = stopcond; // stop when stopcond returns true
TOptimizer<Method::G_GENETIC> solver(stc);
// Different search methods have to be used depending on the model elevation
if (is_on_floor(po)) {
// We are searching rotations around the three axes x, y, z. Thus the
// problem becomes a 3 dimensional optimization task.
// We can specify the bounds for a dimension in the following way:
auto b = bound(-PI/2, PI/2);
std::vector<XYRotation> inputs = get_chull_rotations(mesh, max_tries);
max_tries = inputs.size();
// Now we start the optimization process with initial angles (0, 0, 0)
auto result = solver.optimize_max(objfunc,
libnest2d::opt::initvals(0.0, 0.0, 0.0),
b, b, b);
// If the model can be placed on the bed directly, we only need to
// check the 3D convex hull face rotations.
// Save the result and fck off
rot[0] = std::get<0>(result.optimum);
rot[1] = std::get<1>(result.optimum);
rot[2] = std::get<2>(result.optimum);
auto objfn = [&mesh, &statusfn](const XYRotation &rot) {
statusfn();
Transform3d tr = to_transform3d(rot);
return get_model_supportedness_onfloor(mesh, tr);
};
return rot;
rot = find_min_score<2>(objfn, inputs.begin(), inputs.end(), stopcond);
} else {
// Preparing the optimizer.
size_t gridsize = std::sqrt(max_tries); // 2D grid has gridsize^2 calls
opt::Optimizer<opt::AlgBruteForce> solver(opt::StopCriteria{}
.max_iterations(max_tries)
.stop_condition(stopcond),
gridsize);
// We are searching rotations around only two axes x, y. Thus the
// problem becomes a 2 dimensional optimization task.
// We can specify the bounds for a dimension in the following way:
auto bounds = opt::bounds({ {-PI, PI}, {-PI, PI} });
auto result = solver.to_min().optimize(
[&mesh, &statusfn] (const XYRotation &rot)
{
statusfn();
return get_model_supportedness(mesh, to_transform3d(rot));
}, opt::initvals({0., 0.}), bounds);
// Save the result and fck off
rot = result.optimum;
}
return {rot[0], rot[1]};
}
double get_model_supportedness(const SLAPrintObject &po, const Transform3d &tr)
{
TriangleMesh mesh = po.model_object()->raw_mesh();
mesh.require_shared_vertices();
return is_on_floor(po) ? get_model_supportedness_onfloor(mesh, tr) :
get_model_supportedness(mesh, tr);
}
}
}} // namespace Slic3r::sla

View file

@ -4,9 +4,11 @@
#include <functional>
#include <array>
#include <libslic3r/Point.hpp>
namespace Slic3r {
class ModelObject;
class SLAPrintObject;
namespace sla {
@ -25,14 +27,17 @@ namespace sla {
*
* @return Returns the rotations around each axis (x, y, z)
*/
std::array<double, 3> find_best_rotation(
const ModelObject& modelobj,
Vec2d find_best_rotation(
const SLAPrintObject& modelobj,
float accuracy = 1.0f,
std::function<void(unsigned)> statuscb = [] (unsigned) {},
std::function<bool()> stopcond = [] () { return false; }
);
}
}
double get_model_supportedness(const SLAPrintObject &mesh,
const Transform3d & tr);
} // namespace sla
} // namespace Slic3r
#endif // SLAROTFINDER_HPP

View file

@ -163,10 +163,10 @@ static std::vector<SupportPointGenerator::MyLayer> make_layers(
SupportPointGenerator::MyLayer &layer_below = layers[layer_id - 1];
//FIXME WTF?
const float layer_height = (layer_id!=0 ? heights[layer_id]-heights[layer_id-1] : heights[0]);
const float safe_angle = 5.f * (float(M_PI)/180.f); // smaller number - less supports
const float between_layers_offset = float(scale_(layer_height / std::tan(safe_angle)));
const float safe_angle = 35.f * (float(M_PI)/180.f); // smaller number - less supports
const float between_layers_offset = scaled<float>(layer_height * std::tan(safe_angle));
const float slope_angle = 75.f * (float(M_PI)/180.f); // smaller number - less supports
const float slope_offset = float(scale_(layer_height / std::tan(slope_angle)));
const float slope_offset = scaled<float>(layer_height * std::tan(slope_angle));
//FIXME This has a quadratic time complexity, it will be excessively slow for many tiny islands.
for (SupportPointGenerator::Structure &top : layer_above.islands) {
for (SupportPointGenerator::Structure &bottom : layer_below.islands) {
@ -181,6 +181,25 @@ static std::vector<SupportPointGenerator::MyLayer> make_layers(
Polygons bottom_polygons = top.polygons_below();
top.overhangs = diff_ex(top_polygons, bottom_polygons);
if (! top.overhangs.empty()) {
// Produce 2 bands around the island, a safe band for dangling overhangs
// and an unsafe band for sloped overhangs.
// These masks include the original island
auto dangl_mask = offset(bottom_polygons, between_layers_offset, ClipperLib::jtSquare);
auto overh_mask = offset(bottom_polygons, slope_offset, ClipperLib::jtSquare);
// Absolutely hopeless overhangs are those outside the unsafe band
top.overhangs = diff_ex(top_polygons, overh_mask);
// Now cut out the supported core from the safe band
// and cut the safe band from the unsafe band to get distinct
// zones.
overh_mask = diff(overh_mask, dangl_mask);
dangl_mask = diff(dangl_mask, bottom_polygons);
top.dangling_areas = intersection_ex(top_polygons, dangl_mask);
top.overhangs_slopes = intersection_ex(top_polygons, overh_mask);
top.overhangs_area = 0.f;
std::vector<std::pair<ExPolygon*, float>> expolys_with_areas;
for (ExPolygon &ex : top.overhangs) {
@ -196,8 +215,6 @@ static std::vector<SupportPointGenerator::MyLayer> make_layers(
overhangs_sorted.emplace_back(std::move(*p.first));
top.overhangs = std::move(overhangs_sorted);
top.overhangs_area *= float(SCALING_FACTOR * SCALING_FACTOR);
top.overhangs_slopes = diff_ex(top_polygons, offset(bottom_polygons, slope_offset));
top.dangling_areas = diff_ex(top_polygons, offset(bottom_polygons, between_layers_offset));
}
}
}
@ -256,21 +273,9 @@ void SupportPointGenerator::process(const std::vector<ExPolygons>& slices, const
// Now iterate over all polygons and append new points if needed.
for (Structure &s : layer_top->islands) {
// Penalization resulting from large diff from the last layer:
// s.supports_force_inherited /= std::max(1.f, (layer_height / 0.3f) * e_area / s.area);
s.supports_force_inherited /= std::max(1.f, 0.17f * (s.overhangs_area) / s.area);
//float force_deficit = s.support_force_deficit(m_config.tear_pressure());
if (s.islands_below.empty()) { // completely new island - needs support no doubt
uniformly_cover({ *s.polygon }, s, point_grid, true);
} else if (! s.dangling_areas.empty()) {
// Let's see if there's anything that overlaps enough to need supports:
// What we now have in polygons needs support, regardless of what the forces are, so we can add them.
//FIXME is it an island point or not? Vojtech thinks it is.
uniformly_cover(s.dangling_areas, s, point_grid);
} else if (! s.overhangs_slopes.empty()) {
//FIXME add the support force deficit as a parameter, only cover until the defficiency is covered.
uniformly_cover(s.overhangs_slopes, s, point_grid);
}
add_support_points(s, point_grid);
}
m_throw_on_cancel();
@ -288,6 +293,42 @@ void SupportPointGenerator::process(const std::vector<ExPolygons>& slices, const
}
}
void SupportPointGenerator::add_support_points(SupportPointGenerator::Structure &s, SupportPointGenerator::PointGrid3D &grid3d)
{
// Select each type of surface (overrhang, dangling, slope), derive the support
// force deficit for it and call uniformly conver with the right params
float tp = m_config.tear_pressure();
float current = s.supports_force_total();
static constexpr float SLOPE_DAMPING = .0015f;
static constexpr float DANGL_DAMPING = .09f;
if (s.islands_below.empty()) {
// completely new island - needs support no doubt
// deficit is full, there is nothing below that would hold this island
uniformly_cover({ *s.polygon }, s, s.area * tp, grid3d, IslandCoverageFlags(icfIsNew | icfBoundaryOnly) );
return;
}
auto areafn = [](double sum, auto &p) { return sum + p.area() * SCALING_FACTOR * SCALING_FACTOR; };
if (! s.dangling_areas.empty()) {
// Let's see if there's anything that overlaps enough to need supports:
// What we now have in polygons needs support, regardless of what the forces are, so we can add them.
double a = std::accumulate(s.dangling_areas.begin(), s.dangling_areas.end(), 0., areafn);
uniformly_cover(s.dangling_areas, s, a * tp - current * DANGL_DAMPING * std::sqrt(1. - a / s.area), grid3d);
}
if (! s.overhangs_slopes.empty()) {
double a = std::accumulate(s.overhangs_slopes.begin(), s.overhangs_slopes.end(), 0., areafn);
uniformly_cover(s.overhangs_slopes, s, a * tp - current * SLOPE_DAMPING * std::sqrt(1. - a / s.area), grid3d);
}
if (! s.overhangs.empty()) {
uniformly_cover(s.overhangs, s, s.overhangs_area * tp, grid3d);
}
}
std::vector<Vec2f> sample_expolygon(const ExPolygon &expoly, float samples_per_mm2, std::mt19937 &rng)
{
// Triangulate the polygon with holes into triplets of 3D points.
@ -297,16 +338,16 @@ std::vector<Vec2f> sample_expolygon(const ExPolygon &expoly, float samples_per_m
if (! triangles.empty())
{
// Calculate area of each triangle.
std::vector<float> areas;
areas.reserve(triangles.size() / 3);
auto areas = reserve_vector<float>(triangles.size() / 3);
double aback = 0.;
for (size_t i = 0; i < triangles.size(); ) {
const Vec2f &a = triangles[i ++];
const Vec2f v1 = triangles[i ++] - a;
const Vec2f v2 = triangles[i ++] - a;
areas.emplace_back(0.5f * std::abs(cross2(v1, v2)));
if (i != 3)
// Prefix sum of the areas.
areas.back() += areas[areas.size() - 2];
// Prefix sum of the areas.
areas.emplace_back(aback + 0.5f * std::abs(cross2(v1, v2)));
aback = areas.back();
}
size_t num_samples = size_t(ceil(areas.back() * samples_per_mm2));
@ -316,7 +357,7 @@ std::vector<Vec2f> sample_expolygon(const ExPolygon &expoly, float samples_per_m
double r = random_triangle(rng);
size_t idx_triangle = std::min<size_t>(std::upper_bound(areas.begin(), areas.end(), (float)r) - areas.begin(), areas.size() - 1) * 3;
// Select a random point on the triangle.
double u = float(sqrt(random_float(rng)));
double u = float(std::sqrt(random_float(rng)));
double v = float(random_float(rng));
const Vec2f &a = triangles[idx_triangle ++];
const Vec2f &b = triangles[idx_triangle++];
@ -328,16 +369,37 @@ std::vector<Vec2f> sample_expolygon(const ExPolygon &expoly, float samples_per_m
return out;
}
std::vector<Vec2f> sample_expolygon(const ExPolygons &expolys, float samples_per_mm2, std::mt19937 &rng)
{
std::vector<Vec2f> out;
for (const ExPolygon &expoly : expolys)
append(out, sample_expolygon(expoly, samples_per_mm2, rng));
return out;
}
void sample_expolygon_boundary(const ExPolygon & expoly,
float samples_per_mm,
std::vector<Vec2f> &out,
std::mt19937 & rng)
{
double point_stepping_scaled = scale_(1.f) / samples_per_mm;
for (size_t i_contour = 0; i_contour <= expoly.holes.size(); ++ i_contour) {
const Polygon &contour = (i_contour == 0) ? expoly.contour :
expoly.holes[i_contour - 1];
const Points pts = contour.equally_spaced_points(point_stepping_scaled);
for (size_t i = 0; i < pts.size(); ++ i)
out.emplace_back(unscale<float>(pts[i].x()),
unscale<float>(pts[i].y()));
}
}
std::vector<Vec2f> sample_expolygon_with_boundary(const ExPolygon &expoly, float samples_per_mm2, float samples_per_mm_boundary, std::mt19937 &rng)
{
std::vector<Vec2f> out = sample_expolygon(expoly, samples_per_mm2, rng);
double point_stepping_scaled = scale_(1.f) / samples_per_mm_boundary;
for (size_t i_contour = 0; i_contour <= expoly.holes.size(); ++ i_contour) {
const Polygon &contour = (i_contour == 0) ? expoly.contour : expoly.holes[i_contour - 1];
const Points pts = contour.equally_spaced_points(point_stepping_scaled);
for (size_t i = 0; i < pts.size(); ++ i)
out.emplace_back(unscale<float>(pts[i].x()), unscale<float>(pts[i].y()));
}
sample_expolygon_boundary(expoly, samples_per_mm_boundary, out, rng);
return out;
}
@ -359,17 +421,17 @@ static inline std::vector<Vec2f> poisson_disk_from_samples(const std::vector<Vec
}
// Assign the raw samples to grid cells, sort the grid cells lexicographically.
struct RawSample {
struct RawSample
{
Vec2f coord;
Vec2i cell_id;
RawSample(const Vec2f &crd = {}, const Vec2i &id = {}): coord{crd}, cell_id{id} {}
};
std::vector<RawSample> raw_samples_sorted;
RawSample sample;
for (const Vec2f &pt : raw_samples) {
sample.coord = pt;
sample.cell_id = ((pt - corner_min) / radius).cast<int>();
raw_samples_sorted.emplace_back(sample);
}
auto raw_samples_sorted = reserve_vector<RawSample>(raw_samples.size());
for (const Vec2f &pt : raw_samples)
raw_samples_sorted.emplace_back(pt, ((pt - corner_min) / radius).cast<int>());
std::sort(raw_samples_sorted.begin(), raw_samples_sorted.end(), [](const RawSample &lhs, const RawSample &rhs)
{ return lhs.cell_id.x() < rhs.cell_id.x() || (lhs.cell_id.x() == rhs.cell_id.x() && lhs.cell_id.y() < rhs.cell_id.y()); });
@ -464,11 +526,22 @@ static inline std::vector<Vec2f> poisson_disk_from_samples(const std::vector<Vec
return out;
}
void SupportPointGenerator::uniformly_cover(const ExPolygons& islands, Structure& structure, PointGrid3D &grid3d, bool is_new_island, bool just_one)
void SupportPointGenerator::uniformly_cover(const ExPolygons& islands, Structure& structure, float deficit, PointGrid3D &grid3d, IslandCoverageFlags flags)
{
//int num_of_points = std::max(1, (int)((island.area()*pow(SCALING_FACTOR, 2) * m_config.tear_pressure)/m_config.support_force));
const float support_force_deficit = structure.support_force_deficit(m_config.tear_pressure());
float support_force_deficit = deficit;
auto bb = get_extents(islands);
if (flags & icfIsNew) {
Vec2d bbdim = unscaled(Vec2crd{bb.max - bb.min});
if (bbdim.x() > bbdim.y()) std::swap(bbdim.x(), bbdim.y());
double aspectr = bbdim.y() / bbdim.x();
support_force_deficit *= (1 + aspectr / 2.);
}
if (support_force_deficit < 0)
return;
@ -485,13 +558,18 @@ void SupportPointGenerator::uniformly_cover(const ExPolygons& islands, Structure
float min_spacing = poisson_radius;
//FIXME share the random generator. The random generator may be not so cheap to initialize, also we don't want the random generator to be restarted for each polygon.
std::vector<Vec2f> raw_samples = sample_expolygon_with_boundary(islands, samples_per_mm2, 5.f / poisson_radius, m_rng);
std::vector<Vec2f> raw_samples =
flags & icfBoundaryOnly ?
sample_expolygon_with_boundary(islands, samples_per_mm2,
5.f / poisson_radius, m_rng) :
sample_expolygon(islands, samples_per_mm2, m_rng);
std::vector<Vec2f> poisson_samples;
for (size_t iter = 0; iter < 4; ++ iter) {
poisson_samples = poisson_disk_from_samples(raw_samples, poisson_radius,
[&structure, &grid3d, min_spacing](const Vec2f &pos) {
return grid3d.collides_with(pos, &structure, min_spacing);
return grid3d.collides_with(pos, structure.layer->print_z, min_spacing);
});
if (poisson_samples.size() >= poisson_samples_target || m_config.minimal_distance > poisson_radius-EPSILON)
break;
@ -521,12 +599,13 @@ void SupportPointGenerator::uniformly_cover(const ExPolygons& islands, Structure
poisson_samples.erase(poisson_samples.begin() + poisson_samples_target, poisson_samples.end());
}
for (const Vec2f &pt : poisson_samples) {
m_output.emplace_back(float(pt(0)), float(pt(1)), structure.height, m_config.head_diameter/2.f, is_new_island);
m_output.emplace_back(float(pt(0)), float(pt(1)), structure.zlevel, m_config.head_diameter/2.f, flags & icfIsNew);
structure.supports_force_this_layer += m_config.support_force();
grid3d.insert(pt, &structure);
}
}
void remove_bottom_points(std::vector<SupportPoint> &pts, float lvl)
{
// get iterator to the reorganized vector end

View file

@ -22,8 +22,9 @@ public:
float density_relative {1.f};
float minimal_distance {1.f};
float head_diameter {0.4f};
///////////////
inline float support_force() const { return 7.7f / density_relative; } // a force one point can support (arbitrary force unit)
// Originally calibrated to 7.7f, reduced density by Tamas to 70% which is 11.1 (7.7 / 0.7) to adjust for new algorithm changes in tm_suppt_gen_improve
inline float support_force() const { return 11.1f / density_relative; } // a force one point can support (arbitrary force unit)
inline float tear_pressure() const { return 1.f; } // pressure that the display exerts (the force unit per mm2)
};
@ -38,8 +39,8 @@ public:
struct MyLayer;
struct Structure {
Structure(MyLayer &layer, const ExPolygon& poly, const BoundingBox &bbox, const Vec2f &centroid, float area, float h) :
layer(&layer), polygon(&poly), bbox(bbox), centroid(centroid), area(area), height(h)
Structure(MyLayer &layer, const ExPolygon& poly, const BoundingBox &bbox, const Vec2f &centroid, float area, float h) :
layer(&layer), polygon(&poly), bbox(bbox), centroid(centroid), area(area), zlevel(h)
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
, unique_id(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()))
#endif /* SLA_SUPPORTPOINTGEN_DEBUG */
@ -49,7 +50,7 @@ public:
const BoundingBox bbox;
const Vec2f centroid = Vec2f::Zero();
const float area = 0.f;
float height = 0;
float zlevel = 0;
// How well is this ExPolygon held to the print base?
// Positive number, the higher the better.
float supports_force_this_layer = 0.f;
@ -159,8 +160,8 @@ public:
grid.emplace(cell_id(pt.position), pt);
}
bool collides_with(const Vec2f &pos, Structure *island, float radius) {
Vec3f pos3d(pos.x(), pos.y(), float(island->layer->print_z));
bool collides_with(const Vec2f &pos, float print_z, float radius) {
Vec3f pos3d(pos.x(), pos.y(), print_z);
Vec3i cell = cell_id(pos3d);
std::pair<Grid::const_iterator, Grid::const_iterator> it_pair = grid.equal_range(cell);
if (collides_with(pos3d, radius, it_pair.first, it_pair.second))
@ -198,7 +199,16 @@ private:
SupportPointGenerator::Config m_config;
void process(const std::vector<ExPolygons>& slices, const std::vector<float>& heights);
void uniformly_cover(const ExPolygons& islands, Structure& structure, PointGrid3D &grid3d, bool is_new_island = false, bool just_one = false);
public:
enum IslandCoverageFlags : uint8_t { icfNone = 0x0, icfIsNew = 0x1, icfBoundaryOnly = 0x2 };
private:
void uniformly_cover(const ExPolygons& islands, Structure& structure, float deficit, PointGrid3D &grid3d, IslandCoverageFlags flags = icfNone);
void add_support_points(Structure& structure, PointGrid3D &grid3d);
void project_onto_mesh(std::vector<SupportPoint>& points) const;
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
@ -215,6 +225,9 @@ private:
void remove_bottom_points(std::vector<SupportPoint> &pts, float lvl);
std::vector<Vec2f> sample_expolygon(const ExPolygon &expoly, float samples_per_mm2, std::mt19937 &rng);
void sample_expolygon_boundary(const ExPolygon &expoly, float samples_per_mm, std::vector<Vec2f> &out, std::mt19937 &rng);
}} // namespace Slic3r::sla
#endif // SUPPORTPOINTGENERATOR_HPP

View file

@ -1,7 +1,7 @@
#include <libslic3r/SLA/SupportTreeBuildsteps.hpp>
#include <libslic3r/SLA/SpatIndex.hpp>
#include <libslic3r/Optimizer.hpp>
#include <libslic3r/Optimize/NLoptOptimizer.hpp>
#include <boost/log/trivial.hpp>
namespace Slic3r {

View file

@ -1,3 +1,4 @@
#include <libslic3r/Exception.hpp>
#include <libslic3r/SLAPrintSteps.hpp>
#include <libslic3r/MeshBoolean.hpp>
@ -187,7 +188,7 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po)
}
if (MeshBoolean::cgal::does_self_intersect(*holes_mesh_cgal))
throw std::runtime_error(L("Too much overlapping holes."));
throw Slic3r::SlicingError(L("Too many overlapping holes."));
auto hollowed_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal(hollowed_mesh);
@ -195,7 +196,7 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po)
MeshBoolean::cgal::minus(*hollowed_mesh_cgal, *holes_mesh_cgal);
hollowed_mesh = MeshBoolean::cgal::cgal_to_triangle_mesh(*hollowed_mesh_cgal);
} catch (const std::runtime_error &) {
throw std::runtime_error(L(
throw Slic3r::SlicingError(L(
"Drilling holes into the mesh failed. "
"This is usually caused by broken model. Try to fix it first."));
}
@ -241,7 +242,7 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po)
if(slindex_it == po.m_slice_index.end())
//TRN To be shown at the status bar on SLA slicing error.
throw std::runtime_error(
throw Slic3r::RuntimeError(
L("Slicing had to be stopped due to an internal error: "
"Inconsistent slice index."));
@ -445,7 +446,7 @@ void SLAPrint::Steps::generate_pad(SLAPrintObject &po) {
auto &pad_mesh = po.m_supportdata->support_tree_ptr->retrieve_mesh(sla::MeshType::Pad);
if (!validate_pad(pad_mesh, pcfg))
throw std::runtime_error(
throw Slic3r::SlicingError(
L("No pad can be generated for this model with the "
"current configuration"));
@ -613,7 +614,7 @@ void SLAPrint::Steps::initialize_printer_input()
for(const SliceRecord& slicerecord : o->get_slice_index()) {
if (!slicerecord.is_valid())
throw std::runtime_error(
throw Slic3r::SlicingError(
L("There are unprintable objects. Try to "
"adjust support settings to make the "
"objects printable."));

View file

@ -10,6 +10,8 @@
#include "semver/semver.h"
#include "Exception.hpp"
namespace Slic3r {
@ -38,7 +40,7 @@ public:
{
auto parsed = parse(str);
if (! parsed) {
throw std::runtime_error(std::string("Could not parse version string: ") + str);
throw Slic3r::RuntimeError(std::string("Could not parse version string: ") + str);
}
ver = parsed->ver;
parsed->ver = semver_zero();

View file

@ -1,3 +1,4 @@
#include "Exception.hpp"
#include "TriangleMesh.hpp"
#include "ClipperUtils.hpp"
#include "Geometry.hpp"
@ -70,7 +71,7 @@ TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector<Vec3i> &fac
stl_get_size(&stl);
}
TriangleMesh::TriangleMesh(const indexed_triangle_set &M)
TriangleMesh::TriangleMesh(const indexed_triangle_set &M) : repaired(false)
{
stl.stats.type = inmemory;
@ -420,7 +421,7 @@ std::deque<uint32_t> TriangleMesh::find_unvisited_neighbors(std::vector<unsigned
{
// Make sure we're not operating on a broken mesh.
if (!this->repaired)
throw std::runtime_error("find_unvisited_neighbors() requires repair()");
throw Slic3r::RuntimeError("find_unvisited_neighbors() requires repair()");
// If the visited list is empty, populate it with false for every facet.
if (facet_visited.empty())
@ -683,7 +684,7 @@ void TriangleMeshSlicer::init(const TriangleMesh *_mesh, throw_on_cancel_callbac
{
mesh = _mesh;
if (! mesh->has_shared_vertices())
throw std::invalid_argument("TriangleMeshSlicer was passed a mesh without shared vertices.");
throw Slic3r::InvalidArgument("TriangleMeshSlicer was passed a mesh without shared vertices.");
throw_on_cancel();
facets_edges.assign(_mesh->stl.stats.number_of_facets * 3, -1);

View file

@ -348,11 +348,11 @@ inline std::string get_time_dhm(float time_in_secs)
char buffer[64];
if (days > 0)
::sprintf(buffer, "%dd %dh %dm %ds", days, hours, minutes, (int)time_in_secs);
::sprintf(buffer, "%dd %dh %dm", days, hours, minutes);
else if (hours > 0)
::sprintf(buffer, "%dh %dm %ds", hours, minutes, (int)time_in_secs);
::sprintf(buffer, "%dh %dm", hours, minutes);
else if (minutes > 0)
::sprintf(buffer, "%dm %ds", minutes, (int)time_in_secs);
::sprintf(buffer, "%dm", minutes);
return buffer;
}

View file

@ -1,5 +1,6 @@
#include <exception>
#include "Exception.hpp"
#include "Zipper.hpp"
#include "miniz_extension.hpp"
#include <boost/log/trivial.hpp>
@ -29,7 +30,7 @@ public:
SLIC3R_NORETURN void blow_up() const
{
throw std::runtime_error(formatted_errorstr());
throw Slic3r::RuntimeError(formatted_errorstr());
}
bool is_alive()

View file

@ -6,4 +6,7 @@
#define SLIC3R_VERSION "@SLIC3R_VERSION@"
#define SLIC3R_BUILD_ID "@SLIC3R_BUILD_ID@"
#define GCODEVIEWER_APP_NAME "@GCODEVIEWER_APP_NAME@"
#define GCODEVIEWER_BUILD_ID "@GCODEVIEWER_BUILD_ID@"
#endif /* __SLIC3R_VERSION_H */

View file

@ -173,6 +173,10 @@ set(SLIC3R_GUI_SOURCES
GUI/Search.hpp
GUI/NotificationManager.cpp
GUI/NotificationManager.hpp
GUI/UnsavedChangesDialog.cpp
GUI/UnsavedChangesDialog.hpp
GUI/ExtraRenderers.cpp
GUI/ExtraRenderers.hpp
Utils/Http.cpp
Utils/Http.hpp
Utils/FixModelByWin10.cpp
@ -191,6 +195,8 @@ set(SLIC3R_GUI_SOURCES
Utils/Bonjour.hpp
Utils/PresetUpdater.cpp
Utils/PresetUpdater.hpp
Utils/Process.cpp
Utils/Process.hpp
Utils/Profile.hpp
Utils/UndoRedo.cpp
Utils/UndoRedo.hpp

View file

@ -315,7 +315,7 @@ size_t SnapshotDB::load_db()
// Sort the snapshots by their date/time.
std::sort(m_snapshots.begin(), m_snapshots.end(), [](const Snapshot &s1, const Snapshot &s2) { return s1.time_captured < s2.time_captured; });
if (! errors_cummulative.empty())
throw std::runtime_error(errors_cummulative);
throw Slic3r::RuntimeError(errors_cummulative);
return m_snapshots.size();
}
@ -339,7 +339,7 @@ static void copy_config_dir_single_level(const boost::filesystem::path &path_src
{
if (! boost::filesystem::is_directory(path_dst) &&
! boost::filesystem::create_directory(path_dst))
throw std::runtime_error(std::string("Slic3r was unable to create a directory at ") + path_dst.string());
throw Slic3r::RuntimeError(std::string("Slic3r was unable to create a directory at ") + path_dst.string());
for (auto &dir_entry : boost::filesystem::directory_iterator(path_src))
if (Slic3r::is_ini_file(dir_entry))
@ -429,7 +429,7 @@ const Snapshot& SnapshotDB::restore_snapshot(const std::string &id, AppConfig &a
this->restore_snapshot(snapshot, app_config);
return snapshot;
}
throw std::runtime_error(std::string("Snapshot with id " + id + " was not found."));
throw Slic3r::RuntimeError(std::string("Snapshot with id " + id + " was not found."));
}
void SnapshotDB::restore_snapshot(const Snapshot &snapshot, AppConfig &app_config)
@ -501,7 +501,7 @@ boost::filesystem::path SnapshotDB::create_db_dir()
subdir.make_preferred();
if (! boost::filesystem::is_directory(subdir) &&
! boost::filesystem::create_directory(subdir))
throw std::runtime_error(std::string("Slic3r was unable to create a directory at ") + subdir.string());
throw Slic3r::RuntimeError(std::string("Slic3r was unable to create a directory at ") + subdir.string());
}
return snapshots_dir;
}

View file

@ -324,7 +324,7 @@ std::vector<Index> Index::load_db()
}
if (! errors_cummulative.empty())
throw std::runtime_error(errors_cummulative);
throw Slic3r::RuntimeError(errors_cummulative);
return index_db;
}

View file

@ -1926,7 +1926,7 @@ void _3DScene::extrusionentity_to_verts(const ExtrusionEntity *extrusion_entity,
if (extrusion_entity_collection != nullptr)
extrusionentity_to_verts(*extrusion_entity_collection, print_z, copy, volume);
else {
throw std::runtime_error("Unexpected extrusion_entity type in to_verts()");
throw Slic3r::RuntimeError("Unexpected extrusion_entity type in to_verts()");
}
}
}

View file

@ -37,10 +37,17 @@ void AboutDialogLogo::onRepaint(wxEvent &event)
// CopyrightsDialog
// -----------------------------------------
CopyrightsDialog::CopyrightsDialog()
#if ENABLE_GCODE_VIEWER
: DPIDialog(NULL, wxID_ANY, from_u8((boost::format("%1% - %2%")
% (wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME)
% _utf8(L("Portions copyright"))).str()),
wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
#else
: DPIDialog(NULL, wxID_ANY, from_u8((boost::format("%1% - %2%")
% SLIC3R_APP_NAME
% _utf8(L("Portions copyright"))).str()),
wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
#endif // ENABLE_GCODE_VIEWER
{
this->SetFont(wxGetApp().normal_font());
this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
@ -52,7 +59,7 @@ CopyrightsDialog::CopyrightsDialog()
m_html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition,
wxSize(40 * em_unit(), 20 * em_unit()), wxHW_SCROLLBAR_AUTO);
wxFont font = GetFont();
wxFont font = get_default_font_for_dpi(get_dpi_for_window(this));// GetFont();
const int fs = font.GetPointSize();
const int fs2 = static_cast<int>(1.2f*fs);
int size[] = { fs, fs, fs, fs, fs2, fs2, fs2 };
@ -201,8 +208,13 @@ void CopyrightsDialog::onCloseDialog(wxEvent &)
}
AboutDialog::AboutDialog()
#if ENABLE_GCODE_VIEWER
: DPIDialog(NULL, wxID_ANY, from_u8((boost::format(_utf8(L("About %s"))) % (wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME)).str()), wxDefaultPosition,
wxDefaultSize, /*wxCAPTION*/wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
#else
: DPIDialog(NULL, wxID_ANY, from_u8((boost::format(_utf8(L("About %s"))) % SLIC3R_APP_NAME).str()), wxDefaultPosition,
wxDefaultSize, /*wxCAPTION*/wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
#endif // ENABLE_GCODE_VIEWER
{
SetFont(wxGetApp().normal_font());
@ -214,7 +226,11 @@ AboutDialog::AboutDialog()
main_sizer->Add(hsizer, 0, wxEXPAND | wxALL, 20);
// logo
#if ENABLE_GCODE_VIEWER
m_logo_bitmap = ScalableBitmap(this, wxGetApp().is_editor() ? "PrusaSlicer_192px.png" : "PrusaSlicer-gcodeviewer_192px.png", 192);
#else
m_logo_bitmap = ScalableBitmap(this, "PrusaSlicer_192px.png", 192);
#endif // ENABLE_GCODE_VIEWER
m_logo = new wxStaticBitmap(this, wxID_ANY, m_logo_bitmap.bmp());
hsizer->Add(m_logo, 1, wxALIGN_CENTER_VERTICAL);
@ -223,7 +239,11 @@ AboutDialog::AboutDialog()
// title
{
#if ENABLE_GCODE_VIEWER
wxStaticText* title = new wxStaticText(this, wxID_ANY, wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME, wxDefaultPosition, wxDefaultSize);
#else
wxStaticText* title = new wxStaticText(this, wxID_ANY, SLIC3R_APP_NAME, wxDefaultPosition, wxDefaultSize);
#endif // ENABLE_GCODE_VIEWER
wxFont title_font = GUI::wxGetApp().bold_font();
title_font.SetFamily(wxFONTFAMILY_ROMAN);
title_font.SetPointSize(24);
@ -233,7 +253,7 @@ AboutDialog::AboutDialog()
// version
{
auto version_string = _(L("Version"))+ " " + std::string(SLIC3R_VERSION);
auto version_string = _L("Version")+ " " + std::string(SLIC3R_VERSION);
wxStaticText* version = new wxStaticText(this, wxID_ANY, version_string.c_str(), wxDefaultPosition, wxDefaultSize);
wxFont version_font = GetFont();
#ifdef __WXMSW__
@ -249,7 +269,7 @@ AboutDialog::AboutDialog()
m_html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO/*NEVER*/);
{
m_html->SetMinSize(wxSize(-1, 16 * wxGetApp().em_unit()));
wxFont font = GetFont();
wxFont font = get_default_font_for_dpi(get_dpi_for_window(this));// GetFont();
const auto text_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue());
auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue());
@ -294,7 +314,7 @@ AboutDialog::AboutDialog()
wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxCLOSE);
m_copy_rights_btn_id = NewControlId();
auto copy_rights_btn = new wxButton(this, m_copy_rights_btn_id, _(L("Portions copyright"))+dots);
auto copy_rights_btn = new wxButton(this, m_copy_rights_btn_id, _L("Portions copyright")+dots);
buttons->Insert(0, copy_rights_btn, 0, wxLEFT, 5);
copy_rights_btn->Bind(wxEVT_BUTTON, &AboutDialog::onCopyrightBtn, this);

View file

@ -41,6 +41,36 @@
namespace Slic3r {
bool SlicingProcessCompletedEvent::critical_error() const
{
try {
this->rethrow_exception();
} catch (const Slic3r::SlicingError &ex) {
// Exception derived from SlicingError is non-critical.
return false;
} catch (...) {
}
return true;
}
std::string SlicingProcessCompletedEvent::format_error_message() const
{
std::string error;
try {
this->rethrow_exception();
} catch (const std::bad_alloc& ex) {
wxString errmsg = GUI::from_u8((boost::format(_utf8(L("%s has encountered an error. It was likely caused by running out of memory. "
"If you are sure you have enough RAM on your system, this may also be a bug and we would "
"be glad if you reported it."))) % SLIC3R_APP_NAME).str());
error = std::string(errmsg.ToUTF8()) + "\n\n" + std::string(ex.what());
} catch (std::exception &ex) {
error = ex.what();
} catch (...) {
error = "Unknown C++ exception.";
}
return error;
}
BackgroundSlicingProcess::BackgroundSlicingProcess()
{
boost::filesystem::path temp_path(wxStandardPaths::Get().GetTempDir().utf8_str().data());
@ -109,19 +139,19 @@ void BackgroundSlicingProcess::process_fff()
switch (copy_ret_val) {
case SUCCESS: break; // no error
case FAIL_COPY_FILE:
throw std::runtime_error(_utf8(L("Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?")));
throw Slic3r::RuntimeError(_utf8(L("Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?")));
break;
case FAIL_FILES_DIFFERENT:
throw std::runtime_error((boost::format(_utf8(L("Copying of the temporary G-code to the output G-code failed. There might be problem with target device, please try exporting again or using different device. The corrupted output G-code is at %1%.tmp."))) % export_path).str());
throw Slic3r::RuntimeError((boost::format(_utf8(L("Copying of the temporary G-code to the output G-code failed. There might be problem with target device, please try exporting again or using different device. The corrupted output G-code is at %1%.tmp."))) % export_path).str());
break;
case FAIL_RENAMING:
throw std::runtime_error((boost::format(_utf8(L("Renaming of the G-code after copying to the selected destination folder has failed. Current path is %1%.tmp. Please try exporting again."))) % export_path).str());
throw Slic3r::RuntimeError((boost::format(_utf8(L("Renaming of the G-code after copying to the selected destination folder has failed. Current path is %1%.tmp. Please try exporting again."))) % export_path).str());
break;
case FAIL_CHECK_ORIGIN_NOT_OPENED:
throw std::runtime_error((boost::format(_utf8(L("Copying of the temporary G-code has finished but the original code at %1% couldn't be opened during copy check. The output G-code is at %2%.tmp."))) % m_temp_output_path % export_path).str());
throw Slic3r::RuntimeError((boost::format(_utf8(L("Copying of the temporary G-code has finished but the original code at %1% couldn't be opened during copy check. The output G-code is at %2%.tmp."))) % m_temp_output_path % export_path).str());
break;
case FAIL_CHECK_TARGET_NOT_OPENED:
throw std::runtime_error((boost::format(_utf8(L("Copying of the temporary G-code has finished but the exported code couldn't be opened during copy check. The output G-code is at %1%.tmp."))) % export_path).str());
throw Slic3r::RuntimeError((boost::format(_utf8(L("Copying of the temporary G-code has finished but the exported code couldn't be opened during copy check. The output G-code is at %1%.tmp."))) % export_path).str());
break;
default:
BOOST_LOG_TRIVIAL(warning) << "Unexpected fail code(" << (int)copy_ret_val << ") durring copy_file() to " << export_path << ".";
@ -210,7 +240,7 @@ void BackgroundSlicingProcess::thread_proc()
// Process the background slicing task.
m_state = STATE_RUNNING;
lck.unlock();
std::string error;
std::exception_ptr exception;
try {
assert(m_print != nullptr);
switch(m_print->technology()) {
@ -221,15 +251,8 @@ void BackgroundSlicingProcess::thread_proc()
} catch (CanceledException & /* ex */) {
// Canceled, this is all right.
assert(m_print->canceled());
} catch (const std::bad_alloc& ex) {
wxString errmsg = GUI::from_u8((boost::format(_utf8(L("%s has encountered an error. It was likely caused by running out of memory. "
"If you are sure you have enough RAM on your system, this may also be a bug and we would "
"be glad if you reported it."))) % SLIC3R_APP_NAME).str());
error = std::string(errmsg.ToUTF8()) + "\n\n" + std::string(ex.what());
} catch (std::exception &ex) {
error = ex.what();
} catch (...) {
error = "Unknown C++ exception.";
exception = std::current_exception();
}
m_print->finalize();
lck.lock();
@ -237,9 +260,9 @@ void BackgroundSlicingProcess::thread_proc()
if (m_print->cancel_status() != Print::CANCELED_INTERNAL) {
// Only post the canceled event, if canceled by user.
// Don't post the canceled event, if canceled from Print::apply().
wxCommandEvent evt(m_event_finished_id);
evt.SetString(GUI::from_u8(error));
evt.SetInt(m_print->canceled() ? -1 : (error.empty() ? 1 : 0));
SlicingProcessCompletedEvent evt(m_event_finished_id, 0,
(m_state == STATE_CANCELED) ? SlicingProcessCompletedEvent::Cancelled :
exception ? SlicingProcessCompletedEvent::Error : SlicingProcessCompletedEvent::Finished, exception);
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone());
}
m_print->restart();
@ -299,7 +322,7 @@ bool BackgroundSlicingProcess::start()
// The background processing thread is already running.
return false;
if (! this->idle())
throw std::runtime_error("Cannot start a background task, the worker thread is not idle.");
throw Slic3r::RuntimeError("Cannot start a background task, the worker thread is not idle.");
m_state = STATE_STARTED;
m_print->set_cancel_callback([this](){ this->stop_internal(); });
lck.unlock();
@ -494,7 +517,7 @@ void BackgroundSlicingProcess::prepare_upload()
if (m_print == m_fff_print) {
m_print->set_status(95, _utf8(L("Running post-processing scripts")));
if (copy_file(m_temp_output_path, source_path.string()) != SUCCESS) {
throw std::runtime_error(_utf8(L("Copying of the temporary G-code to the output G-code failed")));
throw Slic3r::RuntimeError(_utf8(L("Copying of the temporary G-code to the output G-code failed")));
}
run_post_process_scripts(source_path.string(), m_fff_print->config());
m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());

View file

@ -37,6 +37,36 @@ public:
PrintBase::SlicingStatus status;
};
class SlicingProcessCompletedEvent : public wxEvent
{
public:
enum StatusType {
Finished,
Cancelled,
Error
};
SlicingProcessCompletedEvent(wxEventType eventType, int winid, StatusType status, std::exception_ptr exception) :
wxEvent(winid, eventType), m_status(status), m_exception(exception) {}
virtual wxEvent* Clone() const { return new SlicingProcessCompletedEvent(*this); }
StatusType status() const { return m_status; }
bool finished() const { return m_status == Finished; }
bool success() const { return m_status == Finished; }
bool cancelled() const { return m_status == Cancelled; }
bool error() const { return m_status == Error; }
// Unhandled error produced by stdlib or a Win32 structured exception, or unhandled Slic3r's own critical exception.
bool critical_error() const;
// Only valid if error()
void rethrow_exception() const { assert(this->error()); assert(m_exception); std::rethrow_exception(m_exception); }
// Produce a human readable message to be displayed by a notification or a message box.
std::string format_error_message() const;
private:
StatusType m_status;
std::exception_ptr m_exception;
};
wxDEFINE_EVENT(EVT_SLICING_UPDATE, SlicingStatusEvent);
// Print step IDs for keeping track of the print state.

View file

@ -21,6 +21,160 @@
namespace Slic3r {
namespace GUI {
BedShape::BedShape(const ConfigOptionPoints& points)
{
auto polygon = Polygon::new_scale(points.values);
// is this a rectangle ?
if (points.size() == 4) {
auto lines = polygon.lines();
if (lines[0].parallel_to(lines[2]) && lines[1].parallel_to(lines[3])) {
// okay, it's a rectangle
// find origin
coordf_t x_min, x_max, y_min, y_max;
x_max = x_min = points.values[0](0);
y_max = y_min = points.values[0](1);
for (auto pt : points.values)
{
x_min = std::min(x_min, pt(0));
x_max = std::max(x_max, pt(0));
y_min = std::min(y_min, pt(1));
y_max = std::max(y_max, pt(1));
}
m_type = Type::Rectangular;
m_rectSize = Vec2d(x_max - x_min, y_max - y_min);
m_rectOrigin = Vec2d(-x_min, -y_min);
return;
}
}
// is this a circle ?
{
// Analyze the array of points.Do they reside on a circle ?
auto center = polygon.bounding_box().center();
std::vector<double> vertex_distances;
double avg_dist = 0;
for (auto pt : polygon.points)
{
double distance = (pt - center).cast<double>().norm();
vertex_distances.push_back(distance);
avg_dist += distance;
}
avg_dist /= vertex_distances.size();
bool defined_value = true;
for (auto el : vertex_distances)
{
if (abs(el - avg_dist) > 10 * SCALED_EPSILON)
defined_value = false;
break;
}
if (defined_value) {
// all vertices are equidistant to center
m_type = Type::Circular;
m_diameter = unscale<double>(avg_dist * 2);
return;
}
}
if (points.size() < 3)
return;
// This is a custom bed shape, use the polygon provided.
m_type = Type::Custom;
}
static std::string get_option_label(BedShape::Parameter param)
{
switch (param) {
case BedShape::Parameter::RectSize : return L("Size");
case BedShape::Parameter::RectOrigin: return L("Origin");
case BedShape::Parameter::Diameter : return L("Diameter");
default: return "";
}
}
void BedShape::append_option_line(ConfigOptionsGroupShp optgroup, Parameter param)
{
ConfigOptionDef def;
if (param == Parameter::RectSize) {
def.type = coPoints;
def.set_default_value(new ConfigOptionPoints{ Vec2d(200, 200) });
def.min = 0;
def.max = 1200;
def.label = get_option_label(param);
def.tooltip = L("Size in X and Y of the rectangular plate.");
Option option(def, "rect_size");
optgroup->append_single_option_line(option);
}
else if (param == Parameter::RectOrigin) {
def.type = coPoints;
def.set_default_value(new ConfigOptionPoints{ Vec2d(0, 0) });
def.min = -600;
def.max = 600;
def.label = get_option_label(param);
def.tooltip = L("Distance of the 0,0 G-code coordinate from the front left corner of the rectangle.");
Option option(def, "rect_origin");
optgroup->append_single_option_line(option);
}
else if (param == Parameter::Diameter) {
def.type = coFloat;
def.set_default_value(new ConfigOptionFloat(200));
def.sidetext = L("mm");
def.label = get_option_label(param);
def.tooltip = L("Diameter of the print bed. It is assumed that origin (0,0) is located in the center.");
Option option(def, "diameter");
optgroup->append_single_option_line(option);
}
}
wxString BedShape::get_name(Type type)
{
switch (type) {
case Type::Rectangular : return _L("Rectangular");
case Type::Circular : return _L("Circular");
case Type::Custom : return _L("Custom");
case Type::Invalid :
default : return _L("Invalid");
}
}
size_t BedShape::get_type()
{
return static_cast<size_t>(m_type == Type::Invalid ? Type::Rectangular : m_type);
}
wxString BedShape::get_full_name_with_params()
{
wxString out = _L("Shape") + ": " + get_name(m_type);
if (m_type == Type::Rectangular) {
out += "\n" + _(get_option_label(Parameter::RectSize)) + ": [" + ConfigOptionPoint(m_rectSize).serialize() + "]";
out += "\n" + _(get_option_label(Parameter::RectOrigin))+ ": [" + ConfigOptionPoint(m_rectOrigin).serialize() + "]";
}
else if (m_type == Type::Circular)
out += "\n" + _L(get_option_label(Parameter::Diameter)) + ": [" + double_to_string(m_diameter) + "]";
return out;
}
void BedShape::apply_optgroup_values(ConfigOptionsGroupShp optgroup)
{
if (m_type == Type::Rectangular || m_type == Type::Invalid) {
optgroup->set_value("rect_size" , new ConfigOptionPoints{ m_rectSize });
optgroup->set_value("rect_origin" , new ConfigOptionPoints{ m_rectOrigin });
}
else if (m_type == Type::Circular)
optgroup->set_value("diameter", double_to_string(m_diameter));
}
void BedShapeDialog::build_dialog(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture, const ConfigOptionString& custom_model)
{
SetFont(wxGetApp().normal_font());
@ -72,50 +226,28 @@ void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const Conf
m_shape_options_book = new wxChoicebook(this, wxID_ANY, wxDefaultPosition, wxSize(25*wxGetApp().em_unit(), -1), wxCHB_TOP);
sbsizer->Add(m_shape_options_book);
auto optgroup = init_shape_options_page(_(L("Rectangular")));
ConfigOptionDef def;
def.type = coPoints;
def.set_default_value(new ConfigOptionPoints{ Vec2d(200, 200) });
def.min = 0;
def.max = 1200;
def.label = L("Size");
def.tooltip = L("Size in X and Y of the rectangular plate.");
Option option(def, "rect_size");
optgroup->append_single_option_line(option);
auto optgroup = init_shape_options_page(BedShape::get_name(BedShape::Type::Rectangular));
BedShape::append_option_line(optgroup, BedShape::Parameter::RectSize);
BedShape::append_option_line(optgroup, BedShape::Parameter::RectOrigin);
def.type = coPoints;
def.set_default_value(new ConfigOptionPoints{ Vec2d(0, 0) });
def.min = -600;
def.max = 600;
def.label = L("Origin");
def.tooltip = L("Distance of the 0,0 G-code coordinate from the front left corner of the rectangle.");
option = Option(def, "rect_origin");
optgroup->append_single_option_line(option);
optgroup = init_shape_options_page(BedShape::get_name(BedShape::Type::Circular));
BedShape::append_option_line(optgroup, BedShape::Parameter::Diameter);
optgroup = init_shape_options_page(_(L("Circular")));
def.type = coFloat;
def.set_default_value(new ConfigOptionFloat(200));
def.sidetext = L("mm");
def.label = L("Diameter");
def.tooltip = L("Diameter of the print bed. It is assumed that origin (0,0) is located in the center.");
option = Option(def, "diameter");
optgroup->append_single_option_line(option);
optgroup = init_shape_options_page(BedShape::get_name(BedShape::Type::Custom));
optgroup = init_shape_options_page(_(L("Custom")));
Line line{ "", "" };
line.full_width = 1;
line.widget = [this](wxWindow* parent) {
wxButton* shape_btn = new wxButton(parent, wxID_ANY, _(L("Load shape from STL...")));
wxButton* shape_btn = new wxButton(parent, wxID_ANY, _L("Load shape from STL..."));
wxSizer* shape_sizer = new wxBoxSizer(wxHORIZONTAL);
shape_sizer->Add(shape_btn, 1, wxEXPAND);
wxSizer* sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(shape_sizer, 1, wxEXPAND);
shape_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e)
{
shape_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) {
load_stl();
}));
});
return sizer;
};
@ -149,10 +281,6 @@ void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const Conf
update_preview();
}
#define SHAPE_RECTANGULAR 0
#define SHAPE_CIRCULAR 1
#define SHAPE_CUSTOM 2
// Called from the constructor.
// Create a panel for a rectangular / circular / custom bed shape.
ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(const wxString& title)
@ -337,83 +465,18 @@ wxPanel* BedShapePanel::init_model_panel()
// with the list of points in the ini file directly.
void BedShapePanel::set_shape(const ConfigOptionPoints& points)
{
auto polygon = Polygon::new_scale(points.values);
BedShape shape(points);
// is this a rectangle ?
if (points.size() == 4) {
auto lines = polygon.lines();
if (lines[0].parallel_to(lines[2]) && lines[1].parallel_to(lines[3])) {
// okay, it's a rectangle
// find origin
coordf_t x_min, x_max, y_min, y_max;
x_max = x_min = points.values[0](0);
y_max = y_min = points.values[0](1);
for (auto pt : points.values)
{
x_min = std::min(x_min, pt(0));
x_max = std::max(x_max, pt(0));
y_min = std::min(y_min, pt(1));
y_max = std::max(y_max, pt(1));
}
m_shape_options_book->SetSelection(shape.get_type());
shape.apply_optgroup_values(m_optgroups[shape.get_type()]);
auto origin = new ConfigOptionPoints{ Vec2d(-x_min, -y_min) };
// Copy the polygon to the canvas, make a copy of the array, if custom shape is selected
if (shape.is_custom())
m_loaded_shape = points.values;
m_shape_options_book->SetSelection(SHAPE_RECTANGULAR);
auto optgroup = m_optgroups[SHAPE_RECTANGULAR];
optgroup->set_value("rect_size", new ConfigOptionPoints{ Vec2d(x_max - x_min, y_max - y_min) });//[x_max - x_min, y_max - y_min]);
optgroup->set_value("rect_origin", origin);
update_shape();
return;
}
}
// is this a circle ?
{
// Analyze the array of points.Do they reside on a circle ?
auto center = polygon.bounding_box().center();
std::vector<double> vertex_distances;
double avg_dist = 0;
for (auto pt: polygon.points)
{
double distance = (pt - center).cast<double>().norm();
vertex_distances.push_back(distance);
avg_dist += distance;
}
avg_dist /= vertex_distances.size();
bool defined_value = true;
for (auto el: vertex_distances)
{
if (abs(el - avg_dist) > 10 * SCALED_EPSILON)
defined_value = false;
break;
}
if (defined_value) {
// all vertices are equidistant to center
m_shape_options_book->SetSelection(SHAPE_CIRCULAR);
auto optgroup = m_optgroups[SHAPE_CIRCULAR];
boost::any ret = wxNumberFormatter::ToString(unscale<double>(avg_dist * 2), 0);
optgroup->set_value("diameter", ret);
update_shape();
return;
}
}
if (points.size() < 3) {
// Invalid polygon.Revert to default bed dimensions.
m_shape_options_book->SetSelection(SHAPE_RECTANGULAR);
auto optgroup = m_optgroups[SHAPE_RECTANGULAR];
optgroup->set_value("rect_size", new ConfigOptionPoints{ Vec2d(200, 200) });
optgroup->set_value("rect_origin", new ConfigOptionPoints{ Vec2d(0, 0) });
update_shape();
return;
}
// This is a custom bed shape, use the polygon provided.
m_shape_options_book->SetSelection(SHAPE_CUSTOM);
// Copy the polygon to the canvas, make a copy of the array.
m_loaded_shape = points.values;
update_shape();
return;
}
void BedShapePanel::update_preview()
@ -426,21 +489,20 @@ void BedShapePanel::update_preview()
void BedShapePanel::update_shape()
{
auto page_idx = m_shape_options_book->GetSelection();
if (page_idx == SHAPE_RECTANGULAR) {
auto opt_group = m_optgroups[page_idx];
BedShape::Type page_type = static_cast<BedShape::Type>(page_idx);
if (page_type == BedShape::Type::Rectangular) {
Vec2d rect_size(Vec2d::Zero());
Vec2d rect_origin(Vec2d::Zero());
try{
rect_size = boost::any_cast<Vec2d>(m_optgroups[SHAPE_RECTANGULAR]->get_value("rect_size")); }
catch (const std::exception & /* e */) {
return;
}
try {
rect_origin = boost::any_cast<Vec2d>(m_optgroups[SHAPE_RECTANGULAR]->get_value("rect_origin"));
}
catch (const std::exception & /* e */) {
return;
}
try { rect_size = boost::any_cast<Vec2d>(opt_group->get_value("rect_size")); }
catch (const std::exception& /* e */) { return; }
try { rect_origin = boost::any_cast<Vec2d>(opt_group->get_value("rect_origin")); }
catch (const std::exception & /* e */) { return; }
auto x = rect_size(0);
auto y = rect_size(1);
// empty strings or '-' or other things
@ -462,14 +524,11 @@ void BedShapePanel::update_shape()
Vec2d(x1, y1),
Vec2d(x0, y1) };
}
else if(page_idx == SHAPE_CIRCULAR) {
else if (page_type == BedShape::Type::Circular) {
double diameter;
try{
diameter = boost::any_cast<double>(m_optgroups[SHAPE_CIRCULAR]->get_value("diameter"));
}
catch (const std::exception & /* e */) {
return;
}
try { diameter = boost::any_cast<double>(opt_group->get_value("diameter")); }
catch (const std::exception & /* e */) { return; }
if (diameter == 0.0) return ;
auto r = diameter / 2;
auto twopi = 2 * PI;
@ -481,7 +540,7 @@ void BedShapePanel::update_shape()
}
m_shape = points;
}
else if (page_idx == SHAPE_CUSTOM)
else if (page_type == BedShape::Type::Custom)
m_shape = m_loaded_shape;
update_preview();

View file

@ -16,6 +16,42 @@ namespace GUI {
class ConfigOptionsGroup;
using ConfigOptionsGroupShp = std::shared_ptr<ConfigOptionsGroup>;
struct BedShape
{
enum class Type {
Rectangular = 0,
Circular,
Custom,
Invalid
};
enum class Parameter {
RectSize,
RectOrigin,
Diameter
};
BedShape(const ConfigOptionPoints& points);
bool is_custom() { return m_type == Type::Custom; }
static void append_option_line(ConfigOptionsGroupShp optgroup, Parameter param);
static wxString get_name(Type type);
// convert Type to size_t
size_t get_type();
wxString get_full_name_with_params();
void apply_optgroup_values(ConfigOptionsGroupShp optgroup);
private:
Type m_type {Type::Invalid};
Vec2d m_rectSize {200, 200};
Vec2d m_rectOrigin {0, 0};
double m_diameter {0};
};
class BedShapePanel : public wxPanel
{
static const std::string NONE;

View file

@ -1,15 +1,15 @@
#include <exception>
namespace Slic3r {
class ConfigError : public std::runtime_error {
using std::runtime_error::runtime_error;
class ConfigError : public Slic3r::RuntimeError {
using Slic3r::RuntimeError::RuntimeError;
};
namespace GUI {
class ConfigGUITypeError : public ConfigError {
using ConfigError::ConfigError;
using ConfigError::ConfigError;
};
}
}
} // namespace GUI
} // namespace Slic3r

View file

@ -114,7 +114,7 @@ ConfigSnapshotDialog::ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db
// text
html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO);
{
wxFont font = wxGetApp().normal_font();//wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
wxFont font = get_default_font_for_dpi(get_dpi_for_window(this));// wxGetApp().normal_font();//wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
#ifdef __WXMSW__
const int fs = font.GetPointSize();
const int fs1 = static_cast<int>(0.8f*fs);
@ -140,7 +140,7 @@ ConfigSnapshotDialog::ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db
void ConfigSnapshotDialog::on_dpi_changed(const wxRect &suggested_rect)
{
wxFont font = GetFont();
wxFont font = get_default_font_for_dpi(get_dpi_for_window(this));// GetFont();
const int fs = font.GetPointSize();
const int fs1 = static_cast<int>(0.8f*fs);
const int fs2 = static_cast<int>(1.1f*fs);

View file

@ -123,7 +123,7 @@ Bundle& BundleMap::prusa_bundle()
{
auto it = find(PresetBundle::PRUSA_BUNDLE);
if (it == end()) {
throw std::runtime_error("ConfigWizard: Internal error in BundleMap: PRUSA_BUNDLE not loaded");
throw Slic3r::RuntimeError("ConfigWizard: Internal error in BundleMap: PRUSA_BUNDLE not loaded");
}
return it->second;

View file

@ -0,0 +1,333 @@
#include "ExtraRenderers.hpp"
#include "wxExtensions.hpp"
#include "GUI.hpp"
#include "I18N.hpp"
#include <wx/dc.h>
#ifdef wxHAS_GENERIC_DATAVIEWCTRL
#include "wx/generic/private/markuptext.h"
#include "wx/generic/private/rowheightcache.h"
#include "wx/generic/private/widthcalc.h"
#endif
/*
#ifdef __WXGTK__
#include "wx/gtk/private.h"
#include "wx/gtk/private/value.h"
#endif
*/
#if wxUSE_ACCESSIBILITY
#include "wx/private/markupparser.h"
#endif // wxUSE_ACCESSIBILITY
using Slic3r::GUI::from_u8;
using Slic3r::GUI::into_u8;
//-----------------------------------------------------------------------------
// DataViewBitmapText
//-----------------------------------------------------------------------------
wxIMPLEMENT_DYNAMIC_CLASS(DataViewBitmapText, wxObject)
IMPLEMENT_VARIANT_OBJECT(DataViewBitmapText)
// ---------------------------------------------------------
// BitmapTextRenderer
// ---------------------------------------------------------
#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
BitmapTextRenderer::BitmapTextRenderer(wxDataViewCellMode mode /*= wxDATAVIEW_CELL_EDITABLE*/,
int align /*= wxDVR_DEFAULT_ALIGNMENT*/):
wxDataViewRenderer(wxT("PrusaDataViewBitmapText"), mode, align)
{
SetMode(mode);
SetAlignment(align);
}
#endif // ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
BitmapTextRenderer::~BitmapTextRenderer()
{
#ifdef SUPPORTS_MARKUP
#ifdef wxHAS_GENERIC_DATAVIEWCTRL
if (m_markupText)
delete m_markupText;
#endif //wxHAS_GENERIC_DATAVIEWCTRL
#endif // SUPPORTS_MARKUP
}
void BitmapTextRenderer::EnableMarkup(bool enable)
{
#ifdef SUPPORTS_MARKUP
#ifdef wxHAS_GENERIC_DATAVIEWCTRL
if (enable) {
if (!m_markupText)
m_markupText = new wxItemMarkupText(wxString());
}
else {
if (m_markupText) {
delete m_markupText;
m_markupText = nullptr;
}
}
#else
is_markupText = enable;
#endif //wxHAS_GENERIC_DATAVIEWCTRL
#endif // SUPPORTS_MARKUP
}
bool BitmapTextRenderer::SetValue(const wxVariant &value)
{
m_value << value;
#ifdef SUPPORTS_MARKUP
#ifdef wxHAS_GENERIC_DATAVIEWCTRL
if (m_markupText)
m_markupText->SetMarkup(m_value.GetText());
/*
#else
#if defined(__WXGTK__)
GValue gvalue = G_VALUE_INIT;
g_value_init(&gvalue, G_TYPE_STRING);
g_value_set_string(&gvalue, wxGTK_CONV_FONT(str.GetText(), GetOwner()->GetOwner()->GetFont()));
g_object_set_property(G_OBJECT(m_renderer/ *.GetText()* /), is_markupText ? "markup" : "text", &gvalue);
g_value_unset(&gvalue);
#endif // __WXGTK__
*/
#endif // wxHAS_GENERIC_DATAVIEWCTRL
#endif // SUPPORTS_MARKUP
return true;
}
bool BitmapTextRenderer::GetValue(wxVariant& WXUNUSED(value)) const
{
return false;
}
#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING && wxUSE_ACCESSIBILITY
wxString BitmapTextRenderer::GetAccessibleDescription() const
{
#ifdef SUPPORTS_MARKUP
if (m_markupText)
return wxMarkupParser::Strip(m_text);
#endif // SUPPORTS_MARKUP
return m_value.GetText();
}
#endif // wxUSE_ACCESSIBILITY && ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
bool BitmapTextRenderer::Render(wxRect rect, wxDC *dc, int state)
{
int xoffset = 0;
const wxBitmap& icon = m_value.GetBitmap();
if (icon.IsOk())
{
#ifdef __APPLE__
wxSize icon_sz = icon.GetScaledSize();
#else
wxSize icon_sz = icon.GetSize();
#endif
dc->DrawBitmap(icon, rect.x, rect.y + (rect.height - icon_sz.y) / 2);
xoffset = icon_sz.x + 4;
}
#if defined(SUPPORTS_MARKUP) && defined(wxHAS_GENERIC_DATAVIEWCTRL)
if (m_markupText)
{
rect.x += xoffset;
m_markupText->Render(GetView(), *dc, rect, 0, GetEllipsizeMode());
}
else
#endif // SUPPORTS_MARKUP && wxHAS_GENERIC_DATAVIEWCTRL
RenderText(m_value.GetText(), xoffset, rect, dc, state);
return true;
}
wxSize BitmapTextRenderer::GetSize() const
{
if (!m_value.GetText().empty())
{
wxSize size;
#if defined(SUPPORTS_MARKUP) && defined(wxHAS_GENERIC_DATAVIEWCTRL)
if (m_markupText)
{
wxDataViewCtrl* const view = GetView();
wxClientDC dc(view);
if (GetAttr().HasFont())
dc.SetFont(GetAttr().GetEffectiveFont(view->GetFont()));
size = m_markupText->Measure(dc);
int lines = m_value.GetText().Freq('\n') + 1;
size.SetHeight(size.GetHeight() * lines);
}
else
#endif // SUPPORTS_MARKUP && wxHAS_GENERIC_DATAVIEWCTRL
size = GetTextExtent(m_value.GetText());
if (m_value.GetBitmap().IsOk())
size.x += m_value.GetBitmap().GetWidth() + 4;
return size;
}
return wxSize(80, 20);
}
wxWindow* BitmapTextRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value)
{
if (can_create_editor_ctrl && !can_create_editor_ctrl())
return nullptr;
DataViewBitmapText data;
data << value;
m_was_unusable_symbol = false;
wxPoint position = labelRect.GetPosition();
if (data.GetBitmap().IsOk()) {
const int bmp_width = data.GetBitmap().GetWidth();
position.x += bmp_width;
labelRect.SetWidth(labelRect.GetWidth() - bmp_width);
}
wxTextCtrl* text_editor = new wxTextCtrl(parent, wxID_ANY, data.GetText(),
position, labelRect.GetSize(), wxTE_PROCESS_ENTER);
text_editor->SetInsertionPointEnd();
text_editor->SelectAll();
return text_editor;
}
bool BitmapTextRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value)
{
wxTextCtrl* text_editor = wxDynamicCast(ctrl, wxTextCtrl);
if (!text_editor || text_editor->GetValue().IsEmpty())
return false;
std::string chosen_name = into_u8(text_editor->GetValue());
const char* unusable_symbols = "<>:/\\|?*\"";
for (size_t i = 0; i < std::strlen(unusable_symbols); i++) {
if (chosen_name.find_first_of(unusable_symbols[i]) != std::string::npos) {
m_was_unusable_symbol = true;
return false;
}
}
// The icon can't be edited so get its old value and reuse it.
wxVariant valueOld;
GetView()->GetModel()->GetValue(valueOld, m_item, /*colName*/0);
DataViewBitmapText bmpText;
bmpText << valueOld;
// But replace the text with the value entered by user.
bmpText.SetText(text_editor->GetValue());
value << bmpText;
return true;
}
// ----------------------------------------------------------------------------
// BitmapChoiceRenderer
// ----------------------------------------------------------------------------
bool BitmapChoiceRenderer::SetValue(const wxVariant& value)
{
m_value << value;
return true;
}
bool BitmapChoiceRenderer::GetValue(wxVariant& value) const
{
value << m_value;
return true;
}
bool BitmapChoiceRenderer::Render(wxRect rect, wxDC* dc, int state)
{
int xoffset = 0;
const wxBitmap& icon = m_value.GetBitmap();
if (icon.IsOk())
{
dc->DrawBitmap(icon, rect.x, rect.y + (rect.height - icon.GetHeight()) / 2);
xoffset = icon.GetWidth() + 4;
if (rect.height==0)
rect.height= icon.GetHeight();
}
RenderText(m_value.GetText(), xoffset, rect, dc, state);
return true;
}
wxSize BitmapChoiceRenderer::GetSize() const
{
wxSize sz = GetTextExtent(m_value.GetText());
if (m_value.GetBitmap().IsOk())
sz.x += m_value.GetBitmap().GetWidth() + 4;
return sz;
}
wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value)
{
if (can_create_editor_ctrl && !can_create_editor_ctrl())
return nullptr;
std::vector<wxBitmap*> icons = get_extruder_color_icons();
if (icons.empty())
return nullptr;
DataViewBitmapText data;
data << value;
auto c_editor = new wxBitmapComboBox(parent, wxID_ANY, wxEmptyString,
labelRect.GetTopLeft(), wxSize(labelRect.GetWidth(), -1),
0, nullptr , wxCB_READONLY);
int i=0;
for (wxBitmap* bmp : icons) {
if (i==0) {
c_editor->Append(_L("default"), *bmp);
++i;
}
c_editor->Append(wxString::Format("%d", i), *bmp);
++i;
}
c_editor->SetSelection(atoi(data.GetText().c_str()));
// to avoid event propagation to other sidebar items
c_editor->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) {
evt.StopPropagation();
// FinishEditing grabs new selection and triggers config update. We better call
// it explicitly, automatic update on KILL_FOCUS didn't work on Linux.
this->FinishEditing();
});
return c_editor;
}
bool BitmapChoiceRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value)
{
wxBitmapComboBox* c = (wxBitmapComboBox*)ctrl;
int selection = c->GetSelection();
if (selection < 0)
return false;
DataViewBitmapText bmpText;
bmpText.SetText(c->GetString(selection));
bmpText.SetBitmap(c->GetItemBitmap(selection));
value << bmpText;
return true;
}

View file

@ -0,0 +1,160 @@
#ifndef slic3r_GUI_ExtraRenderers_hpp_
#define slic3r_GUI_ExtraRenderers_hpp_
#include <wx/dataview.h>
#if wxUSE_MARKUP && wxCHECK_VERSION(3, 1, 1)
#define SUPPORTS_MARKUP
#endif
// ----------------------------------------------------------------------------
// DataViewBitmapText: helper class used by BitmapTextRenderer
// ----------------------------------------------------------------------------
class DataViewBitmapText : public wxObject
{
public:
DataViewBitmapText( const wxString &text = wxEmptyString,
const wxBitmap& bmp = wxNullBitmap) :
m_text(text),
m_bmp(bmp)
{ }
DataViewBitmapText(const DataViewBitmapText &other)
: wxObject(),
m_text(other.m_text),
m_bmp(other.m_bmp)
{ }
void SetText(const wxString &text) { m_text = text; }
wxString GetText() const { return m_text; }
void SetBitmap(const wxBitmap &bmp) { m_bmp = bmp; }
const wxBitmap &GetBitmap() const { return m_bmp; }
bool IsSameAs(const DataViewBitmapText& other) const {
return m_text == other.m_text && m_bmp.IsSameAs(other.m_bmp);
}
bool operator==(const DataViewBitmapText& other) const {
return IsSameAs(other);
}
bool operator!=(const DataViewBitmapText& other) const {
return !IsSameAs(other);
}
private:
wxString m_text;
wxBitmap m_bmp;
wxDECLARE_DYNAMIC_CLASS(DataViewBitmapText);
};
DECLARE_VARIANT_OBJECT(DataViewBitmapText)
// ----------------------------------------------------------------------------
// BitmapTextRenderer
// ----------------------------------------------------------------------------
#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
class BitmapTextRenderer : public wxDataViewRenderer
#else
class BitmapTextRenderer : public wxDataViewCustomRenderer
#endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
{
public:
BitmapTextRenderer(bool use_markup = false,
wxDataViewCellMode mode =
#ifdef __WXOSX__
wxDATAVIEW_CELL_INERT
#else
wxDATAVIEW_CELL_EDITABLE
#endif
, int align = wxDVR_DEFAULT_ALIGNMENT
#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
);
#else
) :
wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align)
{
EnableMarkup(use_markup);
}
#endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
~BitmapTextRenderer();
void EnableMarkup(bool enable = true);
bool SetValue(const wxVariant& value) override;
bool GetValue(wxVariant& value) const override;
#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING && wxUSE_ACCESSIBILITY
virtual wxString GetAccessibleDescription() const override;
#endif // wxUSE_ACCESSIBILITY && ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
virtual bool Render(wxRect cell, wxDC* dc, int state) override;
virtual wxSize GetSize() const override;
bool HasEditorCtrl() const override
{
#ifdef __WXOSX__
return false;
#else
return true;
#endif
}
wxWindow* CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value) override;
bool GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) override;
bool WasCanceled() const { return m_was_unusable_symbol; }
void set_can_create_editor_ctrl_function(std::function<bool()> can_create_fn) { can_create_editor_ctrl = can_create_fn; }
private:
DataViewBitmapText m_value;
bool m_was_unusable_symbol{ false };
std::function<bool()> can_create_editor_ctrl { nullptr };
#ifdef SUPPORTS_MARKUP
#ifdef wxHAS_GENERIC_DATAVIEWCTRL
class wxItemMarkupText* m_markupText { nullptr };;
#else
bool is_markupText {false};
#endif
#endif // SUPPORTS_MARKUP
};
// ----------------------------------------------------------------------------
// BitmapChoiceRenderer
// ----------------------------------------------------------------------------
class BitmapChoiceRenderer : public wxDataViewCustomRenderer
{
public:
BitmapChoiceRenderer(wxDataViewCellMode mode =
#ifdef __WXOSX__
wxDATAVIEW_CELL_INERT
#else
wxDATAVIEW_CELL_EDITABLE
#endif
, int align = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL
) : wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align) {}
bool SetValue(const wxVariant& value);
bool GetValue(wxVariant& value) const;
virtual bool Render(wxRect cell, wxDC* dc, int state) override;
virtual wxSize GetSize() const override;
bool HasEditorCtrl() const override { return true; }
wxWindow* CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value) override;
bool GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) override;
void set_can_create_editor_ctrl_function(std::function<bool()> can_create_fn) { can_create_editor_ctrl = can_create_fn; }
private:
DataViewBitmapText m_value;
std::function<bool()> can_create_editor_ctrl { nullptr };
};
#endif // slic3r_GUI_ExtraRenderers_hpp_

View file

@ -1080,7 +1080,7 @@ boost::any& Choice::get_value()
m_value = static_cast<SLADisplayOrientation>(ret_enum);
else if (m_opt_id.compare("support_pillar_connection_mode") == 0)
m_value = static_cast<SLAPillarConnectionMode>(ret_enum);
else if (m_opt_id == "authorization_type")
else if (m_opt_id == "printhost_authorization_type")
m_value = static_cast<AuthorizationType>(ret_enum);
}
else if (m_opt.gui_type == "f_enum_open") {

View file

@ -766,7 +766,7 @@ const char* FirmwareDialog::priv::avr109_dev_name(Avr109Pid usb_pid) {
return "Original Prusa CW1";
break;
default: throw std::runtime_error((boost::format("Invalid avr109 device USB PID: %1%") % usb_pid.boot).str());
default: throw Slic3r::RuntimeError((boost::format("Invalid avr109 device USB PID: %1%") % usb_pid.boot).str());
}
}

File diff suppressed because it is too large Load diff

View file

@ -18,6 +18,9 @@ namespace GUI {
class GCodeViewer
{
using Color = std::array<float, 3>;
using IndexBuffer = std::vector<unsigned int>;
using MultiIndexBuffer = std::vector<IndexBuffer>;
static const std::vector<Color> Extrusion_Role_Colors;
static const std::vector<Color> Options_Colors;
static const std::vector<Color> Travel_Colors;
@ -112,10 +115,12 @@ class GCodeViewer
{
struct Endpoint
{
// index into the indices buffer
unsigned int i_id{ 0u };
// sequential id
unsigned int s_id{ 0u };
// index of the index buffer
unsigned int b_id{ 0 };
// index into the index buffer
size_t i_id{ 0 };
// sequential id (index into the vertex buffer)
size_t s_id{ 0 };
Vec3f position{ Vec3f::Zero() };
};
@ -134,16 +139,17 @@ class GCodeViewer
bool matches(const GCodeProcessor::MoveVertex& move) const;
size_t vertices_count() const { return last.s_id - first.s_id + 1; }
bool contains(unsigned int id) const { return first.s_id <= id && id <= last.s_id; }
bool contains(size_t id) const { return first.s_id <= id && id <= last.s_id; }
};
// Used to batch the indices needed to render paths
struct RenderPath
{
Color color;
size_t path_id;
unsigned int path_id;
unsigned int index_buffer_id;
std::vector<unsigned int> sizes;
std::vector<size_t> offsets; // use size_t because we need the pointer's size (used in the call glMultiDrawElements())
std::vector<size_t> offsets; // use size_t because we need an unsigned int whose size matches pointer's size (used in the call glMultiDrawElements())
};
// buffer containing data for rendering a specific toolpath type
@ -158,7 +164,7 @@ class GCodeViewer
ERenderPrimitiveType render_primitive_type;
VBuffer vertices;
IBuffer indices;
std::vector<IBuffer> indices;
std::string shader;
std::vector<Path> paths;
@ -166,7 +172,10 @@ class GCodeViewer
bool visible{ false };
void reset();
void add_path(const GCodeProcessor::MoveVertex& move, unsigned int i_id, unsigned int s_id);
// b_id index of buffer contained in this->indices
// i_id index of first index contained in this->indices[b_id]
// s_id index of first vertex contained in this->vertices
void add_path(const GCodeProcessor::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id);
unsigned int indices_per_segment() const {
switch (render_primitive_type)
{
@ -194,6 +203,8 @@ class GCodeViewer
default: { return 0; }
}
}
bool has_data() const { return vertices.id != 0 && !indices.empty() && indices.front().id != 0; }
};
// helper to render shells
@ -264,7 +275,7 @@ class GCodeViewer
#if ENABLE_GCODE_VIEWER_STATISTICS
struct Statistics
{
// times
// time
long long results_time{ 0 };
long long load_time{ 0 };
long long refresh_time{ 0 };
@ -279,15 +290,17 @@ class GCodeViewer
long long indices_gpu_size{ 0 };
long long paths_size{ 0 };
long long render_paths_size{ 0 };
// others
// other
long long travel_segments_count{ 0 };
long long extrude_segments_count{ 0 };
long long max_vertices_in_vertex_buffer{ 0 };
long long max_indices_in_index_buffer{ 0 };
void reset_all() {
reset_times();
reset_opengl();
reset_sizes();
reset_counters();
reset_others();
}
void reset_times() {
@ -311,9 +324,11 @@ class GCodeViewer
render_paths_size = 0;
}
void reset_counters() {
void reset_others() {
travel_segments_count = 0;
extrude_segments_count = 0;
max_vertices_in_vertex_buffer = 0;
max_indices_in_index_buffer = 0;
}
};
#endif // ENABLE_GCODE_VIEWER_STATISTICS
@ -346,8 +361,8 @@ public:
struct Endpoints
{
unsigned int first{ 0 };
unsigned int last{ 0 };
size_t first{ 0 };
size_t last{ 0 };
};
Endpoints endpoints;
@ -371,7 +386,7 @@ public:
private:
unsigned int m_last_result_id{ 0 };
size_t m_vertices_count{ 0 };
size_t m_moves_count{ 0 };
mutable std::vector<TBuffer> m_buffers{ static_cast<size_t>(EMoveType::Extrude) };
// bounding box of toolpaths
BoundingBoxf3 m_paths_bounding_box;
@ -444,7 +459,6 @@ public:
void export_toolpaths_to_obj(const char* filename) const;
private:
void init_shaders();
void load_toolpaths(const GCodeProcessor::Result& gcode_result);
void load_shells(const Print& print, bool initialized);
void refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const;
@ -466,6 +480,7 @@ private:
return in_z_range(path.first.position[2]) || in_z_range(path.last.position[2]);
}
bool is_travel_in_z_range(size_t id) const;
void log_memory_used(const std::string& label, long long additional = 0) const;
};
} // namespace GUI

View file

@ -2732,7 +2732,7 @@ static void load_gcode_retractions(const GCodePreviewData::Retraction& retractio
void GLCanvas3D::load_gcode_preview(const GCodeProcessor::Result& gcode_result)
{
m_gcode_viewer.load(gcode_result, *this->fff_print(), m_initialized);
if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer)
if (wxGetApp().is_editor())
_show_warning_texture_if_needed(WarningTexture::ToolpathOutside);
}
@ -4302,7 +4302,7 @@ void GLCanvas3D::update_ui_from_settings()
#endif // ENABLE_RETINA_GL
#if ENABLE_GCODE_VIEWER
if (wxGetApp().mainframe != nullptr && wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer)
if (wxGetApp().is_editor())
wxGetApp().plater()->get_collapse_toolbar().set_enabled(wxGetApp().app_config->get("show_collapse_button") == "1");
#else
bool enable_collapse = wxGetApp().app_config->get("show_collapse_button") == "1";
@ -4999,7 +4999,7 @@ bool GLCanvas3D::_init_main_toolbar()
return false;
item.name = "settings";
item.icon_filename = "cog_.svg";
item.icon_filename = "settings.svg";
item.tooltip = _u8L("Switch to Settings") + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "2] - " + _u8L("Print Settings Tab") +
"\n" + "[" + GUI::shortkey_ctrl_prefix() + "3] - " + (m_process->current_printer_technology() == ptFFF ? _u8L("Filament Settings Tab") : _u8L("Material Settings Tab")) +
"\n" + "[" + GUI::shortkey_ctrl_prefix() + "4] - " + _u8L("Printer Settings Tab") ;
@ -5011,35 +5011,16 @@ bool GLCanvas3D::_init_main_toolbar()
if (!m_main_toolbar.add_item(item))
return false;
/*
if (!m_main_toolbar.add_separator())
return false;
item.name = "layersediting";
item.icon_filename = "layers_white.svg";
item.tooltip = _utf8(L("Variable layer height"));
item.sprite_id = 11;
item.left.toggable = true;
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); };
item.visibility_callback = [this]()->bool
{
bool res = m_process->current_printer_technology() == ptFFF;
// turns off if changing printer technology
if (!res && m_main_toolbar.is_item_visible("layersediting") && m_main_toolbar.is_item_pressed("layersediting"))
force_main_toolbar_left_action(get_main_toolbar_item_id("layersediting"));
return res;
};
item.enabling_callback = []()->bool { return wxGetApp().plater()->can_layers_editing(); };
if (!m_main_toolbar.add_item(item))
return false;
if (!m_main_toolbar.add_separator())
return false;
*/
item.name = "search";
item.icon_filename = "search_.svg";
item.tooltip = _utf8(L("Search")) + " [" + GUI::shortkey_ctrl_prefix() + "F]";
item.sprite_id = 12;
item.sprite_id = 11;
item.left.toggable = true;
item.left.render_callback = [this](float left, float right, float, float) {
if (m_canvas != nullptr)
{
@ -5053,6 +5034,27 @@ bool GLCanvas3D::_init_main_toolbar()
if (!m_main_toolbar.add_item(item))
return false;
if (!m_main_toolbar.add_separator())
return false;
item.name = "layersediting";
item.icon_filename = "layers_white.svg";
item.tooltip = _utf8(L("Variable layer height"));
item.sprite_id = 12;
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); };
item.visibility_callback = [this]()->bool {
bool res = m_process->current_printer_technology() == ptFFF;
// turns off if changing printer technology
if (!res && m_main_toolbar.is_item_visible("layersediting") && m_main_toolbar.is_item_pressed("layersediting"))
force_main_toolbar_left_action(get_main_toolbar_item_id("layersediting"));
return res;
};
item.enabling_callback = []()->bool { return wxGetApp().plater()->can_layers_editing(); };
item.left.render_callback = GLToolbarItem::Default_Render_Callback;
if (!m_main_toolbar.add_item(item))
return false;
return true;
}
@ -5161,10 +5163,10 @@ bool GLCanvas3D::_init_undoredo_toolbar()
if (!m_undoredo_toolbar.add_item(item))
return false;
/*
if (!m_undoredo_toolbar.add_separator())
return false;
*/
return true;
}
@ -5405,7 +5407,7 @@ void GLCanvas3D::_render_background() const
{
#if ENABLE_GCODE_VIEWER
bool use_error_color = false;
if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer) {
if (wxGetApp().is_editor()) {
use_error_color = m_dynamic_background_enabled;
if (!m_volumes.empty())
use_error_color &= _is_any_volume_outside();
@ -5553,6 +5555,10 @@ void GLCanvas3D::_render_selection_center() const
void GLCanvas3D::_check_and_update_toolbar_icon_scale() const
{
// Don't update a toolbar scale, when we are on a Preview
if (wxGetApp().plater()->is_preview_shown())
return;
float scale = wxGetApp().toolbar_icon_scale();
Size cnv_size = get_canvas_size();
@ -7134,7 +7140,7 @@ void GLCanvas3D::_show_warning_texture_if_needed(WarningTexture::Warning warning
if (!m_volumes.empty())
show = _is_any_volume_outside();
else {
if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer) {
if (wxGetApp().is_editor()) {
BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3();
const BoundingBoxf3& paths_volume = m_gcode_viewer.get_paths_bounding_box();
if (test_volume.radius() > 0.0 && paths_volume.radius() > 0.0)

View file

@ -194,7 +194,7 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt
config.set_key_value(opt_key, new ConfigOptionEnum<SLADisplayOrientation>(boost::any_cast<SLADisplayOrientation>(value)));
else if(opt_key.compare("support_pillar_connection_mode") == 0)
config.set_key_value(opt_key, new ConfigOptionEnum<SLAPillarConnectionMode>(boost::any_cast<SLAPillarConnectionMode>(value)));
else if(opt_key == "authorization_type")
else if(opt_key == "printhost_authorization_type")
config.set_key_value(opt_key, new ConfigOptionEnum<AuthorizationType>(boost::any_cast<AuthorizationType>(value)));
}
break;

View file

@ -1,3 +1,4 @@
#include "libslic3r/Technologies.hpp"
#include "GUI_App.hpp"
#include "GUI_ObjectList.hpp"
#include "GUI_ObjectManipulation.hpp"
@ -30,6 +31,7 @@
#include <wx/dialog.h>
#include <wx/textctrl.h>
#include <wx/splash.h>
#include "libslic3r/Utils.hpp"
#include "libslic3r/Model.hpp"
@ -58,6 +60,8 @@
#include "RemovableDriveManager.hpp"
#include "InstanceCheck.hpp"
#include "NotificationManager.hpp"
#include "UnsavedChangesDialog.hpp"
#include "PresetComboBoxes.hpp"
#ifdef __WXMSW__
#include <dbt.h>
@ -74,6 +78,231 @@ namespace GUI {
class MainFrame;
class SplashScreen : public wxSplashScreen
{
public:
SplashScreen(const wxBitmap& bitmap, long splashStyle, int milliseconds, wxPoint pos = wxDefaultPosition, bool is_decorated = false)
: wxSplashScreen(bitmap, splashStyle, milliseconds, nullptr, wxID_ANY,
wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER | wxFRAME_NO_TASKBAR )
{
wxASSERT(bitmap.IsOk());
m_main_bitmap = bitmap;
if (!is_decorated)
Decorate(m_main_bitmap, pos, true);
m_scale = get_display_scale(pos);
m_font = get_scaled_sys_font(get_splashscreen_display_scale_factor(pos)).Bold().Larger();
if (pos != wxDefaultPosition) {
this->SetPosition(pos);
this->CenterOnScreen();
}
}
void SetText(const wxString& text)
{
set_bitmap(m_main_bitmap);
if (!text.empty()) {
wxBitmap bitmap(m_main_bitmap);
wxMemoryDC memDC;
memDC.SelectObject(bitmap);
wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
memDC.SetFont(m_font);
memDC.SetTextForeground(wxColour(237, 107, 33));
memDC.DrawText(text, int(m_scale * 45), int(m_scale * 200));
memDC.SelectObject(wxNullBitmap);
set_bitmap(bitmap);
}
}
static bool Decorate(wxBitmap& bmp, wxPoint screen_pos = wxDefaultPosition, bool force_decor = false)
{
if (!bmp.IsOk())
return false;
float screen_sf = get_splashscreen_display_scale_factor(screen_pos);
float screen_scale = get_display_scale(screen_pos);
if (screen_sf == 1.0) {
// it means that we have just one display or all displays have a same scale
// Scale bitmap for this display and continue the decoration
scale_bitmap(bmp, screen_scale);
}
else if (force_decor) {
// if we are here, it means that bitmap is already scaled for the main display
// and now we should just scale it th the secondary monitor and continue the decoration
scale_bitmap(bmp, screen_sf);
}
else {
// if screens have different scale and this function is called with force_decor == false
// then just rescale the bitmap for the main display scale
scale_bitmap(bmp, get_display_scale());
return false;
// Decoration will be continued later, from the SplashScreen constructor
}
// use a memory DC to draw directly onto the bitmap
wxMemoryDC memDc(bmp);
// draw an dark grey box at the left of the splashscreen.
// this box will be 2/5 of the weight of the bitmap, and be at the left.
int banner_width = (bmp.GetWidth() / 5) * 2 - 2;
const wxRect banner_rect(wxPoint(0, (bmp.GetHeight() / 9) * 2), wxPoint(banner_width, bmp.GetHeight()));
wxDCBrushChanger bc(memDc, wxBrush(wxColour(51, 51, 51)));
wxDCPenChanger pc(memDc, wxPen(wxColour(51, 51, 51)));
memDc.DrawRectangle(banner_rect);
wxFont sys_font = get_scaled_sys_font(screen_sf);
// title
#if ENABLE_GCODE_VIEWER
wxString title_string = wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME;
#else
wxString title_string = SLIC3R_APP_NAME;
#endif // ENABLE_GCODE_VIEWER
wxFont title_font = sys_font;
title_font.SetPointSize(3 * sys_font.GetPointSize());
// dynamically get the version to display
wxString version_string = _L("Version") + " " + std::string(SLIC3R_VERSION);
wxFont version_font = sys_font.Larger().Larger();
// create a copyright notice that uses the year that this file was compiled
wxString year(__DATE__);
wxString cr_symbol = wxString::FromUTF8("\xc2\xa9");
wxString copyright_string = wxString::Format("%s 2016-%s Prusa Research.\n"
"%s 2011-2018 Alessandro Ranellucci.",
cr_symbol, year.Mid(year.Length() - 4), cr_symbol) + "\n\n";
wxFont copyright_font = sys_font.Larger();
copyright_string += //"Slic3r" + _L("is licensed under the") + _L("GNU Affero General Public License, version 3") + "\n\n" +
_L("PrusaSlicer is based on Slic3r by Alessandro Ranellucci and the RepRap community.") + "\n\n" +
_L("Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Joseph Lenox, Y. Sapir, Mike Sheldrake, Vojtech Bubnik and numerous others.") + "\n\n" +
_L("Splash screen can be disabled from the \"Preferences\"");
word_wrap_string(copyright_string, banner_width, screen_scale);
wxCoord margin = int(screen_scale * 20);
// draw the (orange) labels inside of our black box (at the left of the splashscreen)
memDc.SetTextForeground(wxColour(237, 107, 33));
memDc.SetFont(title_font);
memDc.DrawLabel(title_string, banner_rect.Deflate(margin, 0), wxALIGN_TOP | wxALIGN_LEFT);
memDc.SetFont(version_font);
memDc.DrawLabel(version_string, banner_rect.Deflate(margin, 2 * margin), wxALIGN_TOP | wxALIGN_LEFT);
memDc.SetFont(copyright_font);
memDc.DrawLabel(copyright_string, banner_rect.Deflate(margin, margin), wxALIGN_BOTTOM | wxALIGN_LEFT);
return true;
}
private:
wxBitmap m_main_bitmap;
wxFont m_font;
float m_scale {1.0};
void set_bitmap(wxBitmap& bmp)
{
m_window->SetBitmap(bmp);
m_window->Refresh();
m_window->Update();
}
static float get_splashscreen_display_scale_factor(wxPoint pos = wxDefaultPosition)
{
if (wxDisplay::GetCount() == 1)
return 1.0;
wxFrame main_screen_fr(nullptr, wxID_ANY, wxEmptyString);
wxFrame splash_screen_fr(nullptr, wxID_ANY, wxEmptyString, pos);
#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT && !defined(__WXGTK__)
int main_dpi = get_dpi_for_window(&main_screen_fr);
int splash_dpi = get_dpi_for_window(&splash_screen_fr);
float sf = (float)splash_dpi / (float)main_dpi;
#else
// initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window.
float sf = (float)splash_screen_fr.GetTextExtent("m").x / (float)main_screen_fr.GetTextExtent("m").x;
#endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT
return sf;
}
static float get_display_scale(wxPoint pos = wxDefaultPosition)
{
// pos equals to wxDefaultPosition, when display is main
wxFrame fr(nullptr, wxID_ANY, wxEmptyString, pos);
#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT && !defined(__WXGTK__)
int dpi = get_dpi_for_window(&fr);
float scale = dpi != DPI_DEFAULT ? (float)dpi / DPI_DEFAULT : 1.0;
#else
// initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window.
float scale = 0.1 * std::max<size_t>(10, fr.GetTextExtent("m").x - 1);
#endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT
return scale;
}
static void scale_bitmap(wxBitmap& bmp, float scale)
{
if (scale == 1.0)
return;
wxImage image = bmp.ConvertToImage();
if (!image.IsOk() || image.GetWidth() == 0 || image.GetHeight() == 0)
return;
int width = int(scale * image.GetWidth());
int height = int(scale * image.GetHeight());
image.Rescale(width, height, wxIMAGE_QUALITY_BILINEAR);
bmp = wxBitmap(std::move(image));
}
static wxFont get_scaled_sys_font(float screen_sf)
{
wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
if (screen_sf != 1.0)
font.SetPointSize(int(screen_sf * (float)font.GetPointSize()));
return font;
}
static void word_wrap_string(wxString& input, int line_px_len, float scalef)
{
// calculate count od symbols in one line according to the scale
int line_len = int((float)line_px_len / (scalef * 10) + 0.5) + 10;
int idx = -1;
int cur_len = 0;
for (size_t i = 0; i < input.Len(); i++)
{
cur_len++;
if (input[i] == ' ')
idx = i;
if (input[i] == '\n')
{
idx = -1;
cur_len = 0;
}
if (cur_len >= line_len && idx >= 0)
{
input[idx] = '\n';
cur_len = static_cast<int>(i) - idx;
}
}
}
};
wxString file_wildcards(FileType file_type, const std::string &custom_extension)
{
static const std::string defaults[FT_SIZE] = {
@ -266,8 +495,15 @@ static void generic_exception_handle()
IMPLEMENT_APP(GUI_App)
#if ENABLE_GCODE_VIEWER
GUI_App::GUI_App(EAppMode mode)
#else
GUI_App::GUI_App()
#endif // ENABLE_GCODE_VIEWER
: wxApp()
#if ENABLE_GCODE_VIEWER
, m_app_mode(mode)
#endif // ENABLE_GCODE_VIEWER
, m_em_unit(10)
, m_imgui(new ImGuiWrapper())
, m_wizard(nullptr)
@ -323,13 +559,19 @@ void GUI_App::init_app_config()
if (!app_config)
app_config = new AppConfig();
#if ENABLE_GCODE_VIEWER
if (is_gcode_viewer())
// disable config save to avoid to mess it up for the editor
app_config->enable_save(false);
#endif // ENABLE_GCODE_VIEWER
// load settings
app_conf_exists = app_config->exists();
if (app_conf_exists) {
std::string error = app_config->load();
if (!error.empty())
// Error while parsing config file. We'll customize the error message and rethrow to be displayed.
throw std::runtime_error(
throw Slic3r::RuntimeError(
_u8L("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" + error);
@ -359,18 +601,18 @@ bool GUI_App::on_init_inner()
wxCHECK_MSG(wxDirExists(resources_dir), false,
wxString::Format("Resources path does not exist or is not a directory: %s", resources_dir));
// Enable this to get the default Win32 COMCTRL32 behavior of static boxes.
// Enable this to get the default Win32 COMCTRL32 behavior of static boxes.
// wxSystemOptions::SetOption("msw.staticbox.optimized-paint", 0);
// Enable this to disable Windows Vista themes for all wxNotebooks. The themes seem to lead to terrible
// performance when working on high resolution multi-display setups.
// wxSystemOptions::SetOption("msw.notebook.themed-background", 0);
// Slic3r::debugf "wxWidgets version %s, Wx version %s\n", wxVERSION_STRING, wxVERSION;
std::string msg = Http::tls_global_init();
std::string ssl_cert_store = app_config->get("tls_accepted_cert_store_location");
bool ssl_accept = app_config->get("tls_cert_store_accepted") == "yes" && ssl_cert_store == Http::tls_system_cert_store();
if (!msg.empty() && !ssl_accept) {
wxRichMessageDialog
dlg(nullptr,
@ -380,34 +622,74 @@ bool GUI_App::on_init_inner()
if (dlg.ShowModal() != wxID_YES) return false;
app_config->set("tls_cert_store_accepted",
dlg.IsCheckBoxChecked() ? "yes" : "no");
dlg.IsCheckBoxChecked() ? "yes" : "no");
app_config->set("tls_accepted_cert_store_location",
dlg.IsCheckBoxChecked() ? Http::tls_system_cert_store() : "");
dlg.IsCheckBoxChecked() ? Http::tls_system_cert_store() : "");
}
app_config->set("version", SLIC3R_VERSION);
app_config->save();
/*
if (wxImage::FindHandler(wxBITMAP_TYPE_JPEG) == nullptr)
wxImage::AddHandler(new wxJPEGHandler());
if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr)
wxImage::AddHandler(new wxPNGHandler());
*/
wxInitAllImageHandlers();
SplashScreen* scrn = nullptr;
if (app_config->get("show_splash_screen") == "1")
{
#if ENABLE_GCODE_VIEWER
wxBitmap bmp(is_editor() ? from_u8(var("splashscreen.jpg")) : from_u8(var("splashscreen-gcodeviewer.jpg")), wxBITMAP_TYPE_JPEG);
#else
wxBitmap bmp(from_u8(var("splashscreen.jpg")), wxBITMAP_TYPE_JPEG);
#endif // ENABLE_GCODE_VIEWER
// Detect position (display) to show the splash screen
// Now this position is equal to the mainframe position
wxPoint splashscreen_pos = wxDefaultPosition;
if (app_config->has("window_mainframe")) {
auto metrics = WindowMetrics::deserialize(app_config->get("window_mainframe"));
if (metrics)
splashscreen_pos = metrics->get_rect().GetPosition();
}
// try to decorate and/or scale the bitmap before splash screen creation
bool is_decorated = SplashScreen::Decorate(bmp, splashscreen_pos);
// create splash screen with updated bmp
scrn = new SplashScreen(bmp.IsOk() ? bmp : create_scaled_bitmap("prusa_slicer_logo", nullptr, 400),
wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_TIMEOUT, 4000, splashscreen_pos, is_decorated);
scrn->SetText(_L("Loading configuration..."));
}
preset_bundle = new PresetBundle();
// just checking for existence of Slic3r::data_dir is not enough : it may be an empty directory
// supplied as argument to --datadir; in that case we should still run the wizard
preset_bundle->setup_directories();
#if ENABLE_GCODE_VIEWER
if (is_editor()) {
#endif // ENABLE_GCODE_VIEWER
#ifdef __WXMSW__
associate_3mf_files();
associate_3mf_files();
#endif // __WXMSW__
preset_updater = new PresetUpdater();
Bind(EVT_SLIC3R_VERSION_ONLINE, [this](const wxCommandEvent &evt) {
app_config->set("version_online", into_u8(evt.GetString()));
app_config->save();
if(this->plater_ != nullptr) {
if (*Semver::parse(SLIC3R_VERSION) < * Semver::parse(into_u8(evt.GetString()))) {
this->plater_->get_notification_manager()->push_notification(NotificationType::NewAppAviable, *(this->plater_->get_current_canvas3D()));
}
}
});
preset_updater = new PresetUpdater();
Bind(EVT_SLIC3R_VERSION_ONLINE, [this](const wxCommandEvent& evt) {
app_config->set("version_online", into_u8(evt.GetString()));
app_config->save();
if (this->plater_ != nullptr) {
if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(into_u8(evt.GetString()))) {
this->plater_->get_notification_manager()->push_notification(NotificationType::NewAppAviable, *(this->plater_->get_current_canvas3D()));
}
}
});
#if ENABLE_GCODE_VIEWER
}
#endif // ENABLE_GCODE_VIEWER
// initialize label colors and fonts
init_label_colours();
@ -435,8 +717,13 @@ bool GUI_App::on_init_inner()
Slic3r::I18N::set_translate_callback(libslic3r_translate_callback);
// application frame
if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr)
wxImage::AddHandler(new wxPNGHandler());
#if ENABLE_GCODE_VIEWER
if (scrn && is_editor())
#else
if (scrn)
#endif // ENABLE_GCODE_VIEWER
scrn->SetText(_L("Creating settings tabs..."));
mainframe = new MainFrame();
// hide settings tabs after first Layout
mainframe->select_tab(0);
@ -470,13 +757,20 @@ bool GUI_App::on_init_inner()
static bool once = true;
if (once) {
once = false;
check_updates(false);
#if ENABLE_GCODE_VIEWER
if (preset_updater != nullptr) {
#endif // ENABLE_GCODE_VIEWER
check_updates(false);
CallAfter([this] {
config_wizard_startup();
preset_updater->slic3r_update_notify();
preset_updater->sync(preset_bundle);
});
#if ENABLE_GCODE_VIEWER
}
#endif // ENABLE_GCODE_VIEWER
CallAfter([this] {
config_wizard_startup();
preset_updater->slic3r_update_notify();
preset_updater->sync(preset_bundle);
});
#ifdef _WIN32
//sets window property to mainframe so other instances can indentify it
OtherInstanceMessageHandler::init_windows_properties(mainframe, m_instance_hash_int);
@ -484,8 +778,16 @@ bool GUI_App::on_init_inner()
}
});
load_current_presets();
#if ENABLE_GCODE_VIEWER
if (is_gcode_viewer()) {
mainframe->update_layout();
if (plater_ != nullptr)
// ensure the selected technology is ptFFF
plater_->set_printer_technology(ptFFF);
}
else
#endif // ENABLE_GCODE_VIEWER
load_current_presets();
mainframe->Show(true);
/* Temporary workaround for the correct behavior of the Scrolled sidebar panel:
@ -1122,10 +1424,10 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
app_layout_changed = dlg.settings_layout_changed();
}
if (app_layout_changed) {
mainframe->GetSizer()->Hide((size_t)0);
// hide full main_sizer for mainFrame
mainframe->GetSizer()->Show(false);
mainframe->update_layout();
mainframe->select_tab(0);
mainframe->GetSizer()->Show((size_t)0);
}
break;
}
@ -1173,29 +1475,66 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
// to notify the user whether he is aware that some preset changes will be lost.
bool GUI_App::check_unsaved_changes(const wxString &header)
{
wxString dirty;
PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology();
for (Tab *tab : tabs_list)
bool has_unsaved_changes = false;
for (Tab* tab : tabs_list)
if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) {
if (dirty.empty())
dirty = tab->title();
else
dirty += wxString(", ") + tab->title();
has_unsaved_changes = true;
break;
}
if (dirty.empty())
// No changes, the application may close or reload presets.
return true;
// Ask the user.
wxString message;
if (! header.empty())
message = header + "\n\n";
message += _(L("The presets on the following tabs were modified")) + ": " + dirty + "\n\n" + _(L("Discard changes and continue anyway?"));
wxMessageDialog dialog(mainframe,
message,
wxString(SLIC3R_APP_NAME) + " - " + _(L("Unsaved Presets")),
wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT);
return dialog.ShowModal() == wxID_YES;
if (has_unsaved_changes)
{
UnsavedChangesDialog dlg(header);
if (dlg.ShowModal() == wxID_CANCEL)
return false;
if (dlg.save_preset()) // save selected changes
{
struct NameType
{
std::string name;
Preset::Type type {Preset::TYPE_INVALID};
};
std::vector<NameType> names_and_types;
// for system/default/external presets we should take an edited name
std::vector<Preset::Type> types;
for (Tab* tab : tabs_list)
if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty())
{
const Preset& preset = tab->get_presets()->get_edited_preset();
if (preset.is_system || preset.is_default || preset.is_external)
types.emplace_back(preset.type);
names_and_types.emplace_back(NameType{ preset.name, preset.type });
}
if (!types.empty()) {
SavePresetDialog save_dlg(types);
if (save_dlg.ShowModal() != wxID_OK)
return false;
for (NameType& nt : names_and_types) {
const std::string name = save_dlg.get_name(nt.type);
if (!name.empty())
nt.name = name;
}
}
for (const NameType& nt : names_and_types)
preset_bundle->save_changes_for_preset(nt.name, nt.type, dlg.get_unselected_options(nt.type));
// if we saved changes to the new presets, we should to
// synchronize config.ini with the current selections.
preset_bundle->export_selections(*app_config);
}
}
return true;
}
bool GUI_App::checked_tab(Tab* tab)

View file

@ -86,10 +86,30 @@ class ConfigWizard;
static wxString dots("", wxConvUTF8);
// Does our wxWidgets version support markup?
// https://github.com/prusa3d/PrusaSlicer/issues/4282#issuecomment-634676371
#if wxUSE_MARKUP && wxCHECK_VERSION(3, 1, 1)
#define SUPPORTS_MARKUP
#endif
class GUI_App : public wxApp
{
#if ENABLE_GCODE_VIEWER
public:
enum class EAppMode : unsigned char
{
Editor,
GCodeViewer
};
private:
#endif // ENABLE_GCODE_VIEWER
bool m_initialized { false };
bool app_conf_exists{ false };
#if ENABLE_GCODE_VIEWER
EAppMode m_app_mode{ EAppMode::Editor };
#endif // ENABLE_GCODE_VIEWER
wxColour m_color_label_modified;
wxColour m_color_label_sys;
@ -119,13 +139,24 @@ class GUI_App : public wxApp
std::unique_ptr <wxSingleInstanceChecker> m_single_instance_checker;
std::string m_instance_hash_string;
size_t m_instance_hash_int;
public:
bool OnInit() override;
bool initialized() const { return m_initialized; }
#if ENABLE_GCODE_VIEWER
explicit GUI_App(EAppMode mode = EAppMode::Editor);
#else
GUI_App();
#endif // ENABLE_GCODE_VIEWER
~GUI_App() override;
#if ENABLE_GCODE_VIEWER
EAppMode get_app_mode() const { return m_app_mode; }
bool is_editor() const { return m_app_mode == EAppMode::Editor; }
bool is_gcode_viewer() const { return m_app_mode == EAppMode::GCodeViewer; }
#endif // ENABLE_GCODE_VIEWER
static std::string get_gl_info(bool format_as_html, bool extensions);
wxGLContext* init_glcontext(wxGLCanvas& canvas);
bool init_opengl();

View file

@ -277,7 +277,11 @@ void ObjectList::create_objects_ctrl()
// column ItemName(Icon+Text) of the view control:
// And Icon can be consisting of several bitmaps
AppendColumn(new wxDataViewColumn(_(L("Name")), new BitmapTextRenderer(this),
BitmapTextRenderer* bmp_text_renderer = new BitmapTextRenderer();
bmp_text_renderer->set_can_create_editor_ctrl_function([this]() {
return m_objects_model->GetItemType(GetSelection()) & (itVolume | itObject);
});
AppendColumn(new wxDataViewColumn(_L("Name"), bmp_text_renderer,
colName, 20*em, wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE));
// column PrintableProperty (Icon) of the view control:
@ -285,11 +289,15 @@ void ObjectList::create_objects_ctrl()
wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE);
// column Extruder of the view control:
AppendColumn(new wxDataViewColumn(_(L("Extruder")), new BitmapChoiceRenderer(),
BitmapChoiceRenderer* bmp_choice_renderer = new BitmapChoiceRenderer();
bmp_choice_renderer->set_can_create_editor_ctrl_function([this]() {
return m_objects_model->GetItemType(GetSelection()) & (itVolume | itLayer | itObject);
});
AppendColumn(new wxDataViewColumn(_L("Extruder"), bmp_choice_renderer,
colExtruder, 8*em, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE));
// column ItemEditing of the view control:
AppendBitmapColumn(_(L("Editing")), colEditing, wxDATAVIEW_CELL_INERT, 3*em,
AppendBitmapColumn(_L("Editing"), colEditing, wxDATAVIEW_CELL_INERT, 3*em,
wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE);
// For some reason under OSX on 4K(5K) monitors in wxDataViewColumn constructor doesn't set width of column.

Some files were not shown because too many files have changed in this diff Show more