Merge branch 'master' of https://github.com/prusa3d/PrusaSlicer into et_world_coordinates
This commit is contained in:
commit
9885df5530
62 changed files with 2034 additions and 1266 deletions
|
@ -34,8 +34,9 @@
|
|||
#
|
||||
# Open preferences (might add item to highlight)
|
||||
# hypertext_type = preferences
|
||||
# hypertext_preferences_page = 0 (values 0-2 according to prefernces tab to be opened)
|
||||
#
|
||||
# hypertext_preferences_page = 2 (values 0-2 according to prefernces tab to be opened)
|
||||
# hypertext_preferences_item = show_collapse_button (name of variable saved in prusaslicer.ini connected to the setting in preferences)
|
||||
#
|
||||
# Open gallery (no aditional var)
|
||||
# hypertext_type = gallery
|
||||
#
|
||||
|
@ -97,6 +98,7 @@ documentation_link = https://help.prusa3d.com/en/article/reload-from-disk_120427
|
|||
text = Hiding sidebar\nDid you know that you can hide the right sidebar using the shortcut <b>Shift+Tab</b>? You can also enable the icon for this from the<a>Preferences.</a>
|
||||
hypertext_type = preferences
|
||||
hypertext_preferences_page = 2
|
||||
hypertext_preferences_item = show_collapse_button
|
||||
|
||||
[hint:Perspective camera]
|
||||
text = Perspective camera\nDid you know that you can use the <b>K</b> key to quickly switch between an orthographic and perspective camera?
|
||||
|
@ -195,7 +197,7 @@ documentation_link = https://help.prusa3d.com/en/article/insert-pause-or-custom-
|
|||
disabled_tags = SLA
|
||||
|
||||
[hint:Configuration snapshots]
|
||||
text = Configuration snapshots\nDid you know that roll back to a complete backup of all system and user profiles? You can view and move back and forth between snapshots using the Configuration - Configuration snapshots menu.
|
||||
text = Configuration snapshots\nDid you know that roll back to a complete backup of all system and user profiles? You can view and move back and forth between snapshots using the Configuration - <a>Configuration snapshots menu</a>.
|
||||
documentation_link = https://help.prusa3d.com/en/article/configuration-snapshots_1776
|
||||
hypertext_type = menubar
|
||||
hypertext_menubar_menu_name = Configuration
|
||||
|
@ -213,6 +215,7 @@ disabled_tags = SLA
|
|||
text = Settings in non-modal window\nDid you know that you can open the Settings in a new non-modal window? This means you can have settings open on one screen and the G-code Preview on the other. Go to the<a>Preferences</a>and select Settings in non-modal window.
|
||||
hypertext_type = preferences
|
||||
hypertext_preferences_page = 2
|
||||
hypertext_preferences_item = dlg_settings_layout_mode
|
||||
|
||||
[hint:Adaptive infills]
|
||||
text = Adaptive infills\nDid you know that you can use the Adaptive cubic and Support cubic infills to decrease the print time and lower the filament consumption? Read more in the documentation.
|
||||
|
|
|
@ -5190,11 +5190,11 @@ msgstr "레이어 및 둘레"
|
|||
|
||||
#: src/slic3r/GUI/Tab.cpp:1043
|
||||
msgid "Vertical shells"
|
||||
msgstr "쉘 높이"
|
||||
msgstr "수직 쉘"
|
||||
|
||||
#: src/slic3r/GUI/Tab.cpp:1054
|
||||
msgid "Horizontal shells"
|
||||
msgstr "쉘 너비"
|
||||
msgstr "수평 쉘"
|
||||
|
||||
#: src/slic3r/GUI/Tab.cpp:1055 src/libslic3r/PrintConfig.cpp:1790
|
||||
msgid "Solid layers"
|
||||
|
|
|
@ -248,11 +248,11 @@ std::string AppConfig::load()
|
|||
bool recovered = false;
|
||||
|
||||
try {
|
||||
ifs.open(AppConfig::config_path());
|
||||
ifs.open(AppConfig::loading_path());
|
||||
#ifdef WIN32
|
||||
// Verify the checksum of the config file without taking just for debugging purpose.
|
||||
if (!verify_config_file_checksum(ifs))
|
||||
BOOST_LOG_TRIVIAL(info) << "The configuration file " << AppConfig::config_path() <<
|
||||
BOOST_LOG_TRIVIAL(info) << "The configuration file " << AppConfig::loading_path() <<
|
||||
" has a wrong MD5 checksum or the checksum is missing. This may indicate a file corruption or a harmless user edit.";
|
||||
|
||||
ifs.seekg(0, boost::nowide::ifstream::beg);
|
||||
|
@ -262,32 +262,32 @@ std::string AppConfig::load()
|
|||
#ifdef WIN32
|
||||
// The configuration file is corrupted, try replacing it with the backup configuration.
|
||||
ifs.close();
|
||||
std::string backup_path = (boost::format("%1%.bak") % AppConfig::config_path()).str();
|
||||
std::string backup_path = (boost::format("%1%.bak") % AppConfig::loading_path()).str();
|
||||
if (boost::filesystem::exists(backup_path)) {
|
||||
// Compute checksum of the configuration backup file and try to load configuration from it when the checksum is correct.
|
||||
boost::nowide::ifstream backup_ifs(backup_path);
|
||||
if (!verify_config_file_checksum(backup_ifs)) {
|
||||
BOOST_LOG_TRIVIAL(error) << format("Both \"%1%\" and \"%2%\" are corrupted. It isn't possible to restore configuration from the backup.", AppConfig::config_path(), backup_path);
|
||||
BOOST_LOG_TRIVIAL(error) << format("Both \"%1%\" and \"%2%\" are corrupted. It isn't possible to restore configuration from the backup.", AppConfig::loading_path(), backup_path);
|
||||
backup_ifs.close();
|
||||
boost::filesystem::remove(backup_path);
|
||||
} else if (std::string error_message; copy_file(backup_path, AppConfig::config_path(), error_message, false) != SUCCESS) {
|
||||
BOOST_LOG_TRIVIAL(error) << format("Configuration file \"%1%\" is corrupted. Failed to restore from backup \"%2%\": %3%", AppConfig::config_path(), backup_path, error_message);
|
||||
} else if (std::string error_message; copy_file(backup_path, AppConfig::loading_path(), error_message, false) != SUCCESS) {
|
||||
BOOST_LOG_TRIVIAL(error) << format("Configuration file \"%1%\" is corrupted. Failed to restore from backup \"%2%\": %3%", AppConfig::loading_path(), backup_path, error_message);
|
||||
backup_ifs.close();
|
||||
boost::filesystem::remove(backup_path);
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(info) << format("Configuration file \"%1%\" was corrupted. It has been succesfully restored from the backup \"%2%\".", AppConfig::config_path(), backup_path);
|
||||
BOOST_LOG_TRIVIAL(info) << format("Configuration file \"%1%\" was corrupted. It has been succesfully restored from the backup \"%2%\".", AppConfig::loading_path(), backup_path);
|
||||
// Try parse configuration file after restore from backup.
|
||||
try {
|
||||
ifs.open(AppConfig::config_path());
|
||||
ifs.open(AppConfig::loading_path());
|
||||
pt::read_ini(ifs, tree);
|
||||
recovered = true;
|
||||
} catch (pt::ptree_error& ex) {
|
||||
BOOST_LOG_TRIVIAL(info) << format("Failed to parse configuration file \"%1%\" after it has been restored from backup: %2%", AppConfig::config_path(), ex.what());
|
||||
BOOST_LOG_TRIVIAL(info) << format("Failed to parse configuration file \"%1%\" after it has been restored from backup: %2%", AppConfig::loading_path(), ex.what());
|
||||
}
|
||||
}
|
||||
} else
|
||||
#endif // WIN32
|
||||
BOOST_LOG_TRIVIAL(info) << format("Failed to parse configuration file \"%1%\": %2%", AppConfig::config_path(), ex.what());
|
||||
BOOST_LOG_TRIVIAL(info) << format("Failed to parse configuration file \"%1%\": %2%", AppConfig::loading_path(), ex.what());
|
||||
if (! recovered) {
|
||||
// Report the initial error of parsing PrusaSlicer.ini.
|
||||
// Error while parsing config file. We'll customize the error message and rethrow to be displayed.
|
||||
|
|
|
@ -148,6 +148,9 @@ public:
|
|||
// Does the config file exist?
|
||||
bool exists();
|
||||
|
||||
void set_loading_path(const std::string& path) { m_loading_path = path; }
|
||||
std::string loading_path() { return (m_loading_path.empty() ? config_path() : m_loading_path); }
|
||||
|
||||
std::vector<std::string> get_recent_projects() const;
|
||||
void set_recent_projects(const std::vector<std::string>& recent_projects);
|
||||
|
||||
|
@ -196,6 +199,8 @@ private:
|
|||
Semver m_orig_version;
|
||||
// Whether the existing version is before system profiles & configuration updating
|
||||
bool m_legacy_datadir;
|
||||
|
||||
std::string m_loading_path;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
|
|
@ -72,10 +72,6 @@ static ConstPrintObjectPtrs get_top_level_objects_with_brim(const Print &print,
|
|||
ConstPrintObjectPtrs island_to_object;
|
||||
for(size_t print_object_idx = 0; print_object_idx < print.objects().size(); ++print_object_idx) {
|
||||
const PrintObject *object = print.objects()[print_object_idx];
|
||||
|
||||
if (! object->has_brim())
|
||||
continue;
|
||||
|
||||
Polygons islands_object;
|
||||
islands_object.reserve(bottom_layers_expolygons[print_object_idx].size());
|
||||
for (const ExPolygon &ex_poly : bottom_layers_expolygons[print_object_idx])
|
||||
|
@ -134,6 +130,9 @@ static Polygons top_level_outer_brim_islands(const ConstPrintObjectPtrs &top_lev
|
|||
{
|
||||
Polygons islands;
|
||||
for (const PrintObject *object : top_level_objects_with_brim) {
|
||||
if (!object->has_brim())
|
||||
continue;
|
||||
|
||||
//FIXME how about the brim type?
|
||||
auto brim_separation = float(scale_(object->config().brim_separation.value));
|
||||
Polygons islands_object;
|
||||
|
@ -177,11 +176,14 @@ static ExPolygons top_level_outer_brim_area(const Print &print
|
|||
if ((brim_type == BrimType::btOuterOnly || brim_type == BrimType::btOuterAndInner) && is_top_outer_brim)
|
||||
append(brim_area_object, diff_ex(offset(ex_poly.contour, brim_width + brim_separation, ClipperLib::jtSquare), offset(ex_poly.contour, brim_separation, ClipperLib::jtSquare)));
|
||||
|
||||
// After 7ff76d07684858fd937ef2f5d863f105a10f798e offset and shrink don't work with CW polygons (holes), so let's make it CCW.
|
||||
Polygons ex_poly_holes_reversed = ex_poly.holes;
|
||||
polygons_reverse(ex_poly_holes_reversed);
|
||||
if (brim_type == BrimType::btOuterOnly || brim_type == BrimType::btNoBrim)
|
||||
append(no_brim_area_object, shrink_ex(ex_poly.holes, no_brim_offset, ClipperLib::jtSquare));
|
||||
append(no_brim_area_object, shrink_ex(ex_poly_holes_reversed, no_brim_offset, ClipperLib::jtSquare));
|
||||
|
||||
if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btNoBrim)
|
||||
append(no_brim_area_object, diff_ex(offset(ex_poly.contour, no_brim_offset, ClipperLib::jtSquare), ex_poly.holes));
|
||||
append(no_brim_area_object, diff_ex(offset(ex_poly.contour, no_brim_offset, ClipperLib::jtSquare), ex_poly_holes_reversed));
|
||||
|
||||
if (brim_type != BrimType::btNoBrim)
|
||||
append(no_brim_area_object, offset_ex(ExPolygon(ex_poly.contour), brim_separation, ClipperLib::jtSquare));
|
||||
|
@ -230,16 +232,19 @@ static ExPolygons inner_brim_area(const Print &print,
|
|||
append(brim_area_object, diff_ex(offset(ex_poly.contour, brim_width + brim_separation, ClipperLib::jtSquare), offset(ex_poly.contour, brim_separation, ClipperLib::jtSquare)));
|
||||
}
|
||||
|
||||
// After 7ff76d07684858fd937ef2f5d863f105a10f798e offset and shrink don't work with CW polygons (holes), so let's make it CCW.
|
||||
Polygons ex_poly_holes_reversed = ex_poly.holes;
|
||||
polygons_reverse(ex_poly_holes_reversed);
|
||||
if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btOuterAndInner)
|
||||
append(brim_area_object, diff_ex(shrink_ex(ex_poly.holes, brim_separation, ClipperLib::jtSquare), shrink_ex(ex_poly.holes, brim_width + brim_separation, ClipperLib::jtSquare)));
|
||||
append(brim_area_object, diff_ex(shrink_ex(ex_poly_holes_reversed, brim_separation, ClipperLib::jtSquare), shrink_ex(ex_poly_holes_reversed, brim_width + brim_separation, ClipperLib::jtSquare)));
|
||||
|
||||
if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btNoBrim)
|
||||
append(no_brim_area_object, diff_ex(offset(ex_poly.contour, no_brim_offset, ClipperLib::jtSquare), ex_poly.holes));
|
||||
append(no_brim_area_object, diff_ex(offset(ex_poly.contour, no_brim_offset, ClipperLib::jtSquare), ex_poly_holes_reversed));
|
||||
|
||||
if (brim_type == BrimType::btOuterOnly || brim_type == BrimType::btNoBrim)
|
||||
append(no_brim_area_object, shrink_ex(ex_poly.holes, no_brim_offset, ClipperLib::jtSquare));
|
||||
append(no_brim_area_object, diff_ex(ExPolygon(ex_poly.contour), shrink_ex(ex_poly_holes_reversed, no_brim_offset, ClipperLib::jtSquare)));
|
||||
|
||||
append(holes_object, ex_poly.holes);
|
||||
append(holes_object, ex_poly_holes_reversed);
|
||||
}
|
||||
append(no_brim_area_object, offset_ex(bottom_layers_expolygons[print_object_idx], brim_separation, ClipperLib::jtSquare));
|
||||
|
||||
|
|
|
@ -114,6 +114,14 @@ add_library(libslic3r STATIC
|
|||
GCodeWriter.hpp
|
||||
Geometry.cpp
|
||||
Geometry.hpp
|
||||
Geometry/Circle.cpp
|
||||
Geometry/Circle.hpp
|
||||
Geometry/MedialAxis.cpp
|
||||
Geometry/MedialAxis.hpp
|
||||
Geometry/Voronoi.hpp
|
||||
Geometry/VoronoiOffset.cpp
|
||||
Geometry/VoronoiOffset.hpp
|
||||
Geometry/VoronoiVisualUtils.hpp
|
||||
Int128.hpp
|
||||
KDTreeIndirect.hpp
|
||||
Layer.cpp
|
||||
|
@ -218,9 +226,6 @@ add_library(libslic3r STATIC
|
|||
TriangleSelector.cpp
|
||||
TriangleSelector.hpp
|
||||
MTUtils.hpp
|
||||
VoronoiOffset.cpp
|
||||
VoronoiOffset.hpp
|
||||
VoronoiVisualUtils.hpp
|
||||
Zipper.hpp
|
||||
Zipper.cpp
|
||||
MinAreaBoundingBox.hpp
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#include "BoundingBox.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "Exception.hpp"
|
||||
#include "Geometry.hpp"
|
||||
#include "Geometry/MedialAxis.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include "Line.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "../ClipperUtils.hpp"
|
||||
#include "../EdgeGrid.hpp"
|
||||
#include "../Geometry.hpp"
|
||||
#include "../Geometry/Circle.hpp"
|
||||
#include "../Point.hpp"
|
||||
#include "../PrintConfig.hpp"
|
||||
#include "../Surface.hpp"
|
||||
|
|
|
@ -769,27 +769,31 @@ std::string CoolingBuffer::apply_layer_cooldown(
|
|||
// Find the 'F' word.
|
||||
const char *fpos = strstr(line_start + 2, " F") + 2;
|
||||
int new_feedrate = current_feedrate;
|
||||
// Modify the F word of the current G-code line.
|
||||
bool modify = false;
|
||||
// Remove the F word from the current G-code line.
|
||||
bool remove = false;
|
||||
assert(fpos != nullptr);
|
||||
if (line->slowdown) {
|
||||
modify = true;
|
||||
new_feedrate = int(floor(60. * line->feedrate + 0.5));
|
||||
} else {
|
||||
new_feedrate = atoi(fpos);
|
||||
if (new_feedrate != current_feedrate) {
|
||||
// Append the line without the comment.
|
||||
new_gcode.append(line_start, end - line_start);
|
||||
current_feedrate = new_feedrate;
|
||||
} else if ((line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) || line->length == 0.) {
|
||||
new_feedrate = line->slowdown ? int(floor(60. * line->feedrate + 0.5)) : atoi(fpos);
|
||||
if (new_feedrate == current_feedrate) {
|
||||
// No need to change the F value.
|
||||
if ((line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) || line->length == 0.)
|
||||
// Feedrate does not change and this line does not move the print head. Skip the complete G-code line including the G-code comment.
|
||||
end = line_end;
|
||||
} else {
|
||||
// Remove the feedrate from the G0/G1 line.
|
||||
modify = true;
|
||||
}
|
||||
else
|
||||
// Remove the feedrate from the G0/G1 line. The G-code line may become empty!
|
||||
remove = true;
|
||||
} else if (line->slowdown) {
|
||||
// The F value will be overwritten.
|
||||
modify = true;
|
||||
} else {
|
||||
// The F value is different from current_feedrate, but not slowed down, thus the G-code line will not be modified.
|
||||
// Emit the line without the comment.
|
||||
new_gcode.append(line_start, end - line_start);
|
||||
current_feedrate = new_feedrate;
|
||||
}
|
||||
if (modify) {
|
||||
if (new_feedrate != current_feedrate) {
|
||||
if (modify || remove) {
|
||||
if (modify) {
|
||||
// Replace the feedrate.
|
||||
new_gcode.append(line_start, fpos - line_start);
|
||||
current_feedrate = new_feedrate;
|
||||
|
@ -805,12 +809,16 @@ std::string CoolingBuffer::apply_layer_cooldown(
|
|||
new_gcode.append(line_start, f - line_start + 1);
|
||||
}
|
||||
// Skip the non-whitespaces of the F parameter up the comment or end of line.
|
||||
for (; fpos != end && *fpos != ' ' && *fpos != ';' && *fpos != '\n'; ++fpos);
|
||||
for (; fpos != end && *fpos != ' ' && *fpos != ';' && *fpos != '\n'; ++ fpos);
|
||||
// Append the rest of the line without the comment.
|
||||
if (fpos < end)
|
||||
// The G-code line is not empty yet. Emit the rest of it.
|
||||
new_gcode.append(fpos, end - fpos);
|
||||
// There should never be an empty G1 statement emited by the filter. Such lines should be removed completely.
|
||||
assert(new_gcode.size() < 4 || new_gcode.substr(new_gcode.size() - 4) != "G1 \n");
|
||||
else if (remove && new_gcode == "G1") {
|
||||
// The G-code line only contained the F word, now it is empty. Remove it completely including the comments.
|
||||
new_gcode.resize(new_gcode.size() - 2);
|
||||
end = line_end;
|
||||
}
|
||||
}
|
||||
// Process the rest of the line.
|
||||
if (end < line_end) {
|
||||
|
|
|
@ -26,185 +26,6 @@
|
|||
|
||||
#include <boost/multiprecision/integer.hpp>
|
||||
|
||||
#ifdef SLIC3R_DEBUG
|
||||
#include "SVG.hpp"
|
||||
#endif
|
||||
|
||||
#ifdef SLIC3R_DEBUG
|
||||
namespace boost { namespace polygon {
|
||||
|
||||
// The following code for the visualization of the boost Voronoi diagram is based on:
|
||||
//
|
||||
// Boost.Polygon library voronoi_graphic_utils.hpp header file
|
||||
// Copyright Andrii Sydorchuk 2010-2012.
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE_1_0.txt or copy at
|
||||
// http://www.boost.org/LICENSE_1_0.txt)
|
||||
template <typename CT>
|
||||
class voronoi_visual_utils {
|
||||
public:
|
||||
// Discretize parabolic Voronoi edge.
|
||||
// Parabolic Voronoi edges are always formed by one point and one segment
|
||||
// from the initial input set.
|
||||
//
|
||||
// Args:
|
||||
// point: input point.
|
||||
// segment: input segment.
|
||||
// max_dist: maximum discretization distance.
|
||||
// discretization: point discretization of the given Voronoi edge.
|
||||
//
|
||||
// Template arguments:
|
||||
// InCT: coordinate type of the input geometries (usually integer).
|
||||
// Point: point type, should model point concept.
|
||||
// Segment: segment type, should model segment concept.
|
||||
//
|
||||
// Important:
|
||||
// discretization should contain both edge endpoints initially.
|
||||
template <class InCT1, class InCT2,
|
||||
template<class> class Point,
|
||||
template<class> class Segment>
|
||||
static
|
||||
typename enable_if<
|
||||
typename gtl_and<
|
||||
typename gtl_if<
|
||||
typename is_point_concept<
|
||||
typename geometry_concept< Point<InCT1> >::type
|
||||
>::type
|
||||
>::type,
|
||||
typename gtl_if<
|
||||
typename is_segment_concept<
|
||||
typename geometry_concept< Segment<InCT2> >::type
|
||||
>::type
|
||||
>::type
|
||||
>::type,
|
||||
void
|
||||
>::type discretize(
|
||||
const Point<InCT1>& point,
|
||||
const Segment<InCT2>& segment,
|
||||
const CT max_dist,
|
||||
std::vector< Point<CT> >* discretization) {
|
||||
// Apply the linear transformation to move start point of the segment to
|
||||
// the point with coordinates (0, 0) and the direction of the segment to
|
||||
// coincide the positive direction of the x-axis.
|
||||
CT segm_vec_x = cast(x(high(segment))) - cast(x(low(segment)));
|
||||
CT segm_vec_y = cast(y(high(segment))) - cast(y(low(segment)));
|
||||
CT sqr_segment_length = segm_vec_x * segm_vec_x + segm_vec_y * segm_vec_y;
|
||||
|
||||
// Compute x-coordinates of the endpoints of the edge
|
||||
// in the transformed space.
|
||||
CT projection_start = sqr_segment_length *
|
||||
get_point_projection((*discretization)[0], segment);
|
||||
CT projection_end = sqr_segment_length *
|
||||
get_point_projection((*discretization)[1], segment);
|
||||
|
||||
// Compute parabola parameters in the transformed space.
|
||||
// Parabola has next representation:
|
||||
// f(x) = ((x-rot_x)^2 + rot_y^2) / (2.0*rot_y).
|
||||
CT point_vec_x = cast(x(point)) - cast(x(low(segment)));
|
||||
CT point_vec_y = cast(y(point)) - cast(y(low(segment)));
|
||||
CT rot_x = segm_vec_x * point_vec_x + segm_vec_y * point_vec_y;
|
||||
CT rot_y = segm_vec_x * point_vec_y - segm_vec_y * point_vec_x;
|
||||
|
||||
// Save the last point.
|
||||
Point<CT> last_point = (*discretization)[1];
|
||||
discretization->pop_back();
|
||||
|
||||
// Use stack to avoid recursion.
|
||||
std::stack<CT> point_stack;
|
||||
point_stack.push(projection_end);
|
||||
CT cur_x = projection_start;
|
||||
CT cur_y = parabola_y(cur_x, rot_x, rot_y);
|
||||
|
||||
// Adjust max_dist parameter in the transformed space.
|
||||
const CT max_dist_transformed = max_dist * max_dist * sqr_segment_length;
|
||||
while (!point_stack.empty()) {
|
||||
CT new_x = point_stack.top();
|
||||
CT new_y = parabola_y(new_x, rot_x, rot_y);
|
||||
|
||||
// Compute coordinates of the point of the parabola that is
|
||||
// furthest from the current line segment.
|
||||
CT mid_x = (new_y - cur_y) / (new_x - cur_x) * rot_y + rot_x;
|
||||
CT mid_y = parabola_y(mid_x, rot_x, rot_y);
|
||||
|
||||
// Compute maximum distance between the given parabolic arc
|
||||
// and line segment that discretize it.
|
||||
CT dist = (new_y - cur_y) * (mid_x - cur_x) -
|
||||
(new_x - cur_x) * (mid_y - cur_y);
|
||||
dist = dist * dist / ((new_y - cur_y) * (new_y - cur_y) +
|
||||
(new_x - cur_x) * (new_x - cur_x));
|
||||
if (dist <= max_dist_transformed) {
|
||||
// Distance between parabola and line segment is less than max_dist.
|
||||
point_stack.pop();
|
||||
CT inter_x = (segm_vec_x * new_x - segm_vec_y * new_y) /
|
||||
sqr_segment_length + cast(x(low(segment)));
|
||||
CT inter_y = (segm_vec_x * new_y + segm_vec_y * new_x) /
|
||||
sqr_segment_length + cast(y(low(segment)));
|
||||
discretization->push_back(Point<CT>(inter_x, inter_y));
|
||||
cur_x = new_x;
|
||||
cur_y = new_y;
|
||||
} else {
|
||||
point_stack.push(mid_x);
|
||||
}
|
||||
}
|
||||
|
||||
// Update last point.
|
||||
discretization->back() = last_point;
|
||||
}
|
||||
|
||||
private:
|
||||
// Compute y(x) = ((x - a) * (x - a) + b * b) / (2 * b).
|
||||
static CT parabola_y(CT x, CT a, CT b) {
|
||||
return ((x - a) * (x - a) + b * b) / (b + b);
|
||||
}
|
||||
|
||||
// Get normalized length of the distance between:
|
||||
// 1) point projection onto the segment
|
||||
// 2) start point of the segment
|
||||
// Return this length divided by the segment length. This is made to avoid
|
||||
// sqrt computation during transformation from the initial space to the
|
||||
// transformed one and vice versa. The assumption is made that projection of
|
||||
// the point lies between the start-point and endpoint of the segment.
|
||||
template <class InCT,
|
||||
template<class> class Point,
|
||||
template<class> class Segment>
|
||||
static
|
||||
typename enable_if<
|
||||
typename gtl_and<
|
||||
typename gtl_if<
|
||||
typename is_point_concept<
|
||||
typename geometry_concept< Point<int> >::type
|
||||
>::type
|
||||
>::type,
|
||||
typename gtl_if<
|
||||
typename is_segment_concept<
|
||||
typename geometry_concept< Segment<long> >::type
|
||||
>::type
|
||||
>::type
|
||||
>::type,
|
||||
CT
|
||||
>::type get_point_projection(
|
||||
const Point<CT>& point, const Segment<InCT>& segment) {
|
||||
CT segment_vec_x = cast(x(high(segment))) - cast(x(low(segment)));
|
||||
CT segment_vec_y = cast(y(high(segment))) - cast(y(low(segment)));
|
||||
CT point_vec_x = x(point) - cast(x(low(segment)));
|
||||
CT point_vec_y = y(point) - cast(y(low(segment)));
|
||||
CT sqr_segment_length =
|
||||
segment_vec_x * segment_vec_x + segment_vec_y * segment_vec_y;
|
||||
CT vec_dot = segment_vec_x * point_vec_x + segment_vec_y * point_vec_y;
|
||||
return vec_dot / sqr_segment_length;
|
||||
}
|
||||
|
||||
template <typename InCT>
|
||||
static CT cast(const InCT& value) {
|
||||
return static_cast<CT>(value);
|
||||
}
|
||||
};
|
||||
|
||||
} } // namespace boost::polygon
|
||||
#endif
|
||||
|
||||
using namespace boost::polygon; // provides also high() and low()
|
||||
|
||||
namespace Slic3r { namespace Geometry {
|
||||
|
||||
// This implementation is based on Andrew's monotone chain 2D convex hull algorithm
|
||||
|
@ -337,93 +158,6 @@ double rad2deg_dir(double angle)
|
|||
return rad2deg(angle);
|
||||
}
|
||||
|
||||
Point circle_center_taubin_newton(const Points::const_iterator& input_begin, const Points::const_iterator& input_end, size_t cycles)
|
||||
{
|
||||
Vec2ds tmp;
|
||||
tmp.reserve(std::distance(input_begin, input_end));
|
||||
std::transform(input_begin, input_end, std::back_inserter(tmp), [] (const Point& in) { return unscale(in); } );
|
||||
Vec2d center = circle_center_taubin_newton(tmp.cbegin(), tmp.end(), cycles);
|
||||
return Point::new_scale(center.x(), center.y());
|
||||
}
|
||||
|
||||
/// Adapted from work in "Circular and Linear Regression: Fitting circles and lines by least squares", pg 126
|
||||
/// Returns a point corresponding to the center of a circle for which all of the points from input_begin to input_end
|
||||
/// lie on.
|
||||
Vec2d circle_center_taubin_newton(const Vec2ds::const_iterator& input_begin, const Vec2ds::const_iterator& input_end, size_t cycles)
|
||||
{
|
||||
// calculate the centroid of the data set
|
||||
const Vec2d sum = std::accumulate(input_begin, input_end, Vec2d(0,0));
|
||||
const size_t n = std::distance(input_begin, input_end);
|
||||
const double n_flt = static_cast<double>(n);
|
||||
const Vec2d centroid { sum / n_flt };
|
||||
|
||||
// Compute the normalized moments of the data set.
|
||||
double Mxx = 0, Myy = 0, Mxy = 0, Mxz = 0, Myz = 0, Mzz = 0;
|
||||
for (auto it = input_begin; it < input_end; ++it) {
|
||||
// center/normalize the data.
|
||||
double Xi {it->x() - centroid.x()};
|
||||
double Yi {it->y() - centroid.y()};
|
||||
double Zi {Xi*Xi + Yi*Yi};
|
||||
Mxy += (Xi*Yi);
|
||||
Mxx += (Xi*Xi);
|
||||
Myy += (Yi*Yi);
|
||||
Mxz += (Xi*Zi);
|
||||
Myz += (Yi*Zi);
|
||||
Mzz += (Zi*Zi);
|
||||
}
|
||||
|
||||
// divide by number of points to get the moments
|
||||
Mxx /= n_flt;
|
||||
Myy /= n_flt;
|
||||
Mxy /= n_flt;
|
||||
Mxz /= n_flt;
|
||||
Myz /= n_flt;
|
||||
Mzz /= n_flt;
|
||||
|
||||
// Compute the coefficients of the characteristic polynomial for the circle
|
||||
// eq 5.60
|
||||
const double Mz {Mxx + Myy}; // xx + yy = z
|
||||
const double Cov_xy {Mxx*Myy - Mxy*Mxy}; // this shows up a couple times so cache it here.
|
||||
const double C3 {4.0*Mz};
|
||||
const double C2 {-3.0*(Mz*Mz) - Mzz};
|
||||
const double C1 {Mz*(Mzz - (Mz*Mz)) + 4.0*Mz*Cov_xy - (Mxz*Mxz) - (Myz*Myz)};
|
||||
const double C0 {(Mxz*Mxz)*Myy + (Myz*Myz)*Mxx - 2.0*Mxz*Myz*Mxy - Cov_xy*(Mzz - (Mz*Mz))};
|
||||
|
||||
const double C22 = {C2 + C2};
|
||||
const double C33 = {C3 + C3 + C3};
|
||||
|
||||
// solve the characteristic polynomial with Newton's method.
|
||||
double xnew = 0.0;
|
||||
double ynew = 1e20;
|
||||
|
||||
for (size_t i = 0; i < cycles; ++i) {
|
||||
const double yold {ynew};
|
||||
ynew = C0 + xnew * (C1 + xnew*(C2 + xnew * C3));
|
||||
if (std::abs(ynew) > std::abs(yold)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Geometry: Fit is going in the wrong direction.\n";
|
||||
return Vec2d(std::nan(""), std::nan(""));
|
||||
}
|
||||
const double Dy {C1 + xnew*(C22 + xnew*C33)};
|
||||
|
||||
const double xold {xnew};
|
||||
xnew = xold - (ynew / Dy);
|
||||
|
||||
if (std::abs((xnew-xold) / xnew) < 1e-12) i = cycles; // converged, we're done here
|
||||
|
||||
if (xnew < 0) {
|
||||
// reset, we went negative
|
||||
xnew = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
// compute the determinant and the circle's parameters now that we've solved.
|
||||
double DET = xnew*xnew - xnew*Mz + Cov_xy;
|
||||
|
||||
Vec2d center(Mxz * (Myy - xnew) - Myz * Mxy, Myz * (Mxx - xnew) - Mxz*Mxy);
|
||||
center /= (DET * 2.);
|
||||
return center + centroid;
|
||||
}
|
||||
|
||||
void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval)
|
||||
{
|
||||
Polygons pp;
|
||||
|
@ -649,212 +383,6 @@ arrange(size_t total_parts, const Vec2d &part_size, coordf_t dist, const Boundin
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef SLIC3R_DEBUG
|
||||
// The following code for the visualization of the boost Voronoi diagram is based on:
|
||||
//
|
||||
// Boost.Polygon library voronoi_visualizer.cpp file
|
||||
// Copyright Andrii Sydorchuk 2010-2012.
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE_1_0.txt or copy at
|
||||
// http://www.boost.org/LICENSE_1_0.txt)
|
||||
namespace Voronoi { namespace Internal {
|
||||
|
||||
typedef double coordinate_type;
|
||||
typedef boost::polygon::point_data<coordinate_type> point_type;
|
||||
typedef boost::polygon::segment_data<coordinate_type> segment_type;
|
||||
typedef boost::polygon::rectangle_data<coordinate_type> rect_type;
|
||||
typedef boost::polygon::voronoi_diagram<coordinate_type> VD;
|
||||
typedef VD::cell_type cell_type;
|
||||
typedef VD::cell_type::source_index_type source_index_type;
|
||||
typedef VD::cell_type::source_category_type source_category_type;
|
||||
typedef VD::edge_type edge_type;
|
||||
typedef VD::cell_container_type cell_container_type;
|
||||
typedef VD::cell_container_type vertex_container_type;
|
||||
typedef VD::edge_container_type edge_container_type;
|
||||
typedef VD::const_cell_iterator const_cell_iterator;
|
||||
typedef VD::const_vertex_iterator const_vertex_iterator;
|
||||
typedef VD::const_edge_iterator const_edge_iterator;
|
||||
|
||||
static const std::size_t EXTERNAL_COLOR = 1;
|
||||
|
||||
inline void color_exterior(const VD::edge_type* edge)
|
||||
{
|
||||
if (edge->color() == EXTERNAL_COLOR)
|
||||
return;
|
||||
edge->color(EXTERNAL_COLOR);
|
||||
edge->twin()->color(EXTERNAL_COLOR);
|
||||
const VD::vertex_type* v = edge->vertex1();
|
||||
if (v == NULL || !edge->is_primary())
|
||||
return;
|
||||
v->color(EXTERNAL_COLOR);
|
||||
const VD::edge_type* e = v->incident_edge();
|
||||
do {
|
||||
color_exterior(e);
|
||||
e = e->rot_next();
|
||||
} while (e != v->incident_edge());
|
||||
}
|
||||
|
||||
inline point_type retrieve_point(const std::vector<segment_type> &segments, const cell_type& cell)
|
||||
{
|
||||
assert(cell.source_category() == SOURCE_CATEGORY_SEGMENT_START_POINT || cell.source_category() == SOURCE_CATEGORY_SEGMENT_END_POINT);
|
||||
return (cell.source_category() == SOURCE_CATEGORY_SEGMENT_START_POINT) ? low(segments[cell.source_index()]) : high(segments[cell.source_index()]);
|
||||
}
|
||||
|
||||
inline void clip_infinite_edge(const std::vector<segment_type> &segments, const edge_type& edge, coordinate_type bbox_max_size, std::vector<point_type>* clipped_edge)
|
||||
{
|
||||
const cell_type& cell1 = *edge.cell();
|
||||
const cell_type& cell2 = *edge.twin()->cell();
|
||||
point_type origin, direction;
|
||||
// Infinite edges could not be created by two segment sites.
|
||||
if (cell1.contains_point() && cell2.contains_point()) {
|
||||
point_type p1 = retrieve_point(segments, cell1);
|
||||
point_type p2 = retrieve_point(segments, cell2);
|
||||
origin.x((p1.x() + p2.x()) * 0.5);
|
||||
origin.y((p1.y() + p2.y()) * 0.5);
|
||||
direction.x(p1.y() - p2.y());
|
||||
direction.y(p2.x() - p1.x());
|
||||
} else {
|
||||
origin = cell1.contains_segment() ? retrieve_point(segments, cell2) : retrieve_point(segments, cell1);
|
||||
segment_type segment = cell1.contains_segment() ? segments[cell1.source_index()] : segments[cell2.source_index()];
|
||||
coordinate_type dx = high(segment).x() - low(segment).x();
|
||||
coordinate_type dy = high(segment).y() - low(segment).y();
|
||||
if ((low(segment) == origin) ^ cell1.contains_point()) {
|
||||
direction.x(dy);
|
||||
direction.y(-dx);
|
||||
} else {
|
||||
direction.x(-dy);
|
||||
direction.y(dx);
|
||||
}
|
||||
}
|
||||
coordinate_type koef = bbox_max_size / (std::max)(fabs(direction.x()), fabs(direction.y()));
|
||||
if (edge.vertex0() == NULL) {
|
||||
clipped_edge->push_back(point_type(
|
||||
origin.x() - direction.x() * koef,
|
||||
origin.y() - direction.y() * koef));
|
||||
} else {
|
||||
clipped_edge->push_back(
|
||||
point_type(edge.vertex0()->x(), edge.vertex0()->y()));
|
||||
}
|
||||
if (edge.vertex1() == NULL) {
|
||||
clipped_edge->push_back(point_type(
|
||||
origin.x() + direction.x() * koef,
|
||||
origin.y() + direction.y() * koef));
|
||||
} else {
|
||||
clipped_edge->push_back(
|
||||
point_type(edge.vertex1()->x(), edge.vertex1()->y()));
|
||||
}
|
||||
}
|
||||
|
||||
inline void sample_curved_edge(const std::vector<segment_type> &segments, const edge_type& edge, std::vector<point_type> &sampled_edge, coordinate_type max_dist)
|
||||
{
|
||||
point_type point = edge.cell()->contains_point() ?
|
||||
retrieve_point(segments, *edge.cell()) :
|
||||
retrieve_point(segments, *edge.twin()->cell());
|
||||
segment_type segment = edge.cell()->contains_point() ?
|
||||
segments[edge.twin()->cell()->source_index()] :
|
||||
segments[edge.cell()->source_index()];
|
||||
::boost::polygon::voronoi_visual_utils<coordinate_type>::discretize(point, segment, max_dist, &sampled_edge);
|
||||
}
|
||||
|
||||
} /* namespace Internal */ } // namespace Voronoi
|
||||
|
||||
static inline void dump_voronoi_to_svg(const Lines &lines, /* const */ boost::polygon::voronoi_diagram<double> &vd, const ThickPolylines *polylines, const char *path)
|
||||
{
|
||||
const double scale = 0.2;
|
||||
const std::string inputSegmentPointColor = "lightseagreen";
|
||||
const coord_t inputSegmentPointRadius = coord_t(0.09 * scale / SCALING_FACTOR);
|
||||
const std::string inputSegmentColor = "lightseagreen";
|
||||
const coord_t inputSegmentLineWidth = coord_t(0.03 * scale / SCALING_FACTOR);
|
||||
|
||||
const std::string voronoiPointColor = "black";
|
||||
const coord_t voronoiPointRadius = coord_t(0.06 * scale / SCALING_FACTOR);
|
||||
const std::string voronoiLineColorPrimary = "black";
|
||||
const std::string voronoiLineColorSecondary = "green";
|
||||
const std::string voronoiArcColor = "red";
|
||||
const coord_t voronoiLineWidth = coord_t(0.02 * scale / SCALING_FACTOR);
|
||||
|
||||
const bool internalEdgesOnly = false;
|
||||
const bool primaryEdgesOnly = false;
|
||||
|
||||
BoundingBox bbox = BoundingBox(lines);
|
||||
bbox.min(0) -= coord_t(1. / SCALING_FACTOR);
|
||||
bbox.min(1) -= coord_t(1. / SCALING_FACTOR);
|
||||
bbox.max(0) += coord_t(1. / SCALING_FACTOR);
|
||||
bbox.max(1) += coord_t(1. / SCALING_FACTOR);
|
||||
|
||||
::Slic3r::SVG svg(path, bbox);
|
||||
|
||||
if (polylines != NULL)
|
||||
svg.draw(*polylines, "lime", "lime", voronoiLineWidth);
|
||||
|
||||
// bbox.scale(1.2);
|
||||
// For clipping of half-lines to some reasonable value.
|
||||
// The line will then be clipped by the SVG viewer anyway.
|
||||
const double bbox_dim_max = double(bbox.max(0) - bbox.min(0)) + double(bbox.max(1) - bbox.min(1));
|
||||
// For the discretization of the Voronoi parabolic segments.
|
||||
const double discretization_step = 0.0005 * bbox_dim_max;
|
||||
|
||||
// Make a copy of the input segments with the double type.
|
||||
std::vector<Voronoi::Internal::segment_type> segments;
|
||||
for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++ it)
|
||||
segments.push_back(Voronoi::Internal::segment_type(
|
||||
Voronoi::Internal::point_type(double(it->a(0)), double(it->a(1))),
|
||||
Voronoi::Internal::point_type(double(it->b(0)), double(it->b(1)))));
|
||||
|
||||
// Color exterior edges.
|
||||
for (boost::polygon::voronoi_diagram<double>::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it)
|
||||
if (!it->is_finite())
|
||||
Voronoi::Internal::color_exterior(&(*it));
|
||||
|
||||
// Draw the end points of the input polygon.
|
||||
for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) {
|
||||
svg.draw(it->a, inputSegmentPointColor, inputSegmentPointRadius);
|
||||
svg.draw(it->b, inputSegmentPointColor, inputSegmentPointRadius);
|
||||
}
|
||||
// Draw the input polygon.
|
||||
for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it)
|
||||
svg.draw(Line(Point(coord_t(it->a(0)), coord_t(it->a(1))), Point(coord_t(it->b(0)), coord_t(it->b(1)))), inputSegmentColor, inputSegmentLineWidth);
|
||||
|
||||
#if 1
|
||||
// Draw voronoi vertices.
|
||||
for (boost::polygon::voronoi_diagram<double>::const_vertex_iterator it = vd.vertices().begin(); it != vd.vertices().end(); ++it)
|
||||
if (! internalEdgesOnly || it->color() != Voronoi::Internal::EXTERNAL_COLOR)
|
||||
svg.draw(Point(coord_t(it->x()), coord_t(it->y())), voronoiPointColor, voronoiPointRadius);
|
||||
|
||||
for (boost::polygon::voronoi_diagram<double>::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it) {
|
||||
if (primaryEdgesOnly && !it->is_primary())
|
||||
continue;
|
||||
if (internalEdgesOnly && (it->color() == Voronoi::Internal::EXTERNAL_COLOR))
|
||||
continue;
|
||||
std::vector<Voronoi::Internal::point_type> samples;
|
||||
std::string color = voronoiLineColorPrimary;
|
||||
if (!it->is_finite()) {
|
||||
Voronoi::Internal::clip_infinite_edge(segments, *it, bbox_dim_max, &samples);
|
||||
if (! it->is_primary())
|
||||
color = voronoiLineColorSecondary;
|
||||
} else {
|
||||
// Store both points of the segment into samples. sample_curved_edge will split the initial line
|
||||
// until the discretization_step is reached.
|
||||
samples.push_back(Voronoi::Internal::point_type(it->vertex0()->x(), it->vertex0()->y()));
|
||||
samples.push_back(Voronoi::Internal::point_type(it->vertex1()->x(), it->vertex1()->y()));
|
||||
if (it->is_curved()) {
|
||||
Voronoi::Internal::sample_curved_edge(segments, *it, samples, discretization_step);
|
||||
color = voronoiArcColor;
|
||||
} else if (! it->is_primary())
|
||||
color = voronoiLineColorSecondary;
|
||||
}
|
||||
for (std::size_t i = 0; i + 1 < samples.size(); ++i)
|
||||
svg.draw(Line(Point(coord_t(samples[i].x()), coord_t(samples[i].y())), Point(coord_t(samples[i+1].x()), coord_t(samples[i+1].y()))), color, voronoiLineWidth);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (polylines != NULL)
|
||||
svg.draw(*polylines, "blue", voronoiLineWidth);
|
||||
|
||||
svg.Close();
|
||||
}
|
||||
#endif /* SLIC3R_DEBUG */
|
||||
|
||||
// Euclidian distance of two boost::polygon points.
|
||||
template<typename T>
|
||||
T dist(const boost::polygon::point_data<T> &p1,const boost::polygon::point_data<T> &p2)
|
||||
|
@ -878,331 +406,6 @@ inline point_type project_point_to_segment(segment_type &seg, point_type &px)
|
|||
return point_type(p0(0) + t*dir(0), p0(1) + t*dir(1));
|
||||
}
|
||||
|
||||
template<typename VD, typename SEGMENTS>
|
||||
inline const typename VD::point_type retrieve_cell_point(const typename VD::cell_type& cell, const SEGMENTS &segments)
|
||||
{
|
||||
assert(cell.source_category() == SOURCE_CATEGORY_SEGMENT_START_POINT || cell.source_category() == SOURCE_CATEGORY_SEGMENT_END_POINT);
|
||||
return (cell.source_category() == SOURCE_CATEGORY_SEGMENT_START_POINT) ? low(segments[cell.source_index()]) : high(segments[cell.source_index()]);
|
||||
}
|
||||
|
||||
template<typename VD, typename SEGMENTS>
|
||||
inline std::pair<typename VD::coord_type, typename VD::coord_type>
|
||||
measure_edge_thickness(const VD &vd, const typename VD::edge_type& edge, const SEGMENTS &segments)
|
||||
{
|
||||
typedef typename VD::coord_type T;
|
||||
const typename VD::point_type pa(edge.vertex0()->x(), edge.vertex0()->y());
|
||||
const typename VD::point_type pb(edge.vertex1()->x(), edge.vertex1()->y());
|
||||
const typename VD::cell_type &cell1 = *edge.cell();
|
||||
const typename VD::cell_type &cell2 = *edge.twin()->cell();
|
||||
if (cell1.contains_segment()) {
|
||||
if (cell2.contains_segment()) {
|
||||
// Both cells contain a linear segment, the left / right cells are symmetric.
|
||||
// Project pa, pb to the left segment.
|
||||
const typename VD::segment_type segment1 = segments[cell1.source_index()];
|
||||
const typename VD::point_type p1a = project_point_to_segment(segment1, pa);
|
||||
const typename VD::point_type p1b = project_point_to_segment(segment1, pb);
|
||||
return std::pair<T, T>(T(2.)*dist(pa, p1a), T(2.)*dist(pb, p1b));
|
||||
} else {
|
||||
// 1st cell contains a linear segment, 2nd cell contains a point.
|
||||
// The medial axis between the cells is a parabolic arc.
|
||||
// Project pa, pb to the left segment.
|
||||
const typename VD::point_type p2 = retrieve_cell_point<VD>(cell2, segments);
|
||||
return std::pair<T, T>(T(2.)*dist(pa, p2), T(2.)*dist(pb, p2));
|
||||
}
|
||||
} else if (cell2.contains_segment()) {
|
||||
// 1st cell contains a point, 2nd cell contains a linear segment.
|
||||
// The medial axis between the cells is a parabolic arc.
|
||||
const typename VD::point_type p1 = retrieve_cell_point<VD>(cell1, segments);
|
||||
return std::pair<T, T>(T(2.)*dist(pa, p1), T(2.)*dist(pb, p1));
|
||||
} else {
|
||||
// Both cells contain a point. The left / right regions are triangular and symmetric.
|
||||
const typename VD::point_type p1 = retrieve_cell_point<VD>(cell1, segments);
|
||||
return std::pair<T, T>(T(2.)*dist(pa, p1), T(2.)*dist(pb, p1));
|
||||
}
|
||||
}
|
||||
|
||||
// Converts the Line instances of Lines vector to VD::segment_type.
|
||||
template<typename VD>
|
||||
class Lines2VDSegments
|
||||
{
|
||||
public:
|
||||
Lines2VDSegments(const Lines &alines) : lines(alines) {}
|
||||
typename VD::segment_type operator[](size_t idx) const {
|
||||
return typename VD::segment_type(
|
||||
typename VD::point_type(typename VD::coord_type(lines[idx].a(0)), typename VD::coord_type(lines[idx].a(1))),
|
||||
typename VD::point_type(typename VD::coord_type(lines[idx].b(0)), typename VD::coord_type(lines[idx].b(1))));
|
||||
}
|
||||
private:
|
||||
const Lines &lines;
|
||||
};
|
||||
|
||||
void
|
||||
MedialAxis::build(ThickPolylines* polylines)
|
||||
{
|
||||
construct_voronoi(this->lines.begin(), this->lines.end(), &this->vd);
|
||||
|
||||
/*
|
||||
// DEBUG: dump all Voronoi edges
|
||||
{
|
||||
for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) {
|
||||
if (edge->is_infinite()) continue;
|
||||
|
||||
ThickPolyline polyline;
|
||||
polyline.points.push_back(Point( edge->vertex0()->x(), edge->vertex0()->y() ));
|
||||
polyline.points.push_back(Point( edge->vertex1()->x(), edge->vertex1()->y() ));
|
||||
polylines->push_back(polyline);
|
||||
}
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
//typedef const VD::vertex_type vert_t;
|
||||
typedef const VD::edge_type edge_t;
|
||||
|
||||
// collect valid edges (i.e. prune those not belonging to MAT)
|
||||
// note: this keeps twins, so it inserts twice the number of the valid edges
|
||||
this->valid_edges.clear();
|
||||
{
|
||||
std::set<const VD::edge_type*> seen_edges;
|
||||
for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) {
|
||||
// if we only process segments representing closed loops, none if the
|
||||
// infinite edges (if any) would be part of our MAT anyway
|
||||
if (edge->is_secondary() || edge->is_infinite()) continue;
|
||||
|
||||
// don't re-validate twins
|
||||
if (seen_edges.find(&*edge) != seen_edges.end()) continue; // TODO: is this needed?
|
||||
seen_edges.insert(&*edge);
|
||||
seen_edges.insert(edge->twin());
|
||||
|
||||
if (!this->validate_edge(&*edge)) continue;
|
||||
this->valid_edges.insert(&*edge);
|
||||
this->valid_edges.insert(edge->twin());
|
||||
}
|
||||
}
|
||||
this->edges = this->valid_edges;
|
||||
|
||||
// iterate through the valid edges to build polylines
|
||||
while (!this->edges.empty()) {
|
||||
const edge_t* edge = *this->edges.begin();
|
||||
|
||||
// start a polyline
|
||||
ThickPolyline polyline;
|
||||
polyline.points.push_back(Point( edge->vertex0()->x(), edge->vertex0()->y() ));
|
||||
polyline.points.push_back(Point( edge->vertex1()->x(), edge->vertex1()->y() ));
|
||||
polyline.width.push_back(this->thickness[edge].first);
|
||||
polyline.width.push_back(this->thickness[edge].second);
|
||||
|
||||
// remove this edge and its twin from the available edges
|
||||
(void)this->edges.erase(edge);
|
||||
(void)this->edges.erase(edge->twin());
|
||||
|
||||
// get next points
|
||||
this->process_edge_neighbors(edge, &polyline);
|
||||
|
||||
// get previous points
|
||||
{
|
||||
ThickPolyline rpolyline;
|
||||
this->process_edge_neighbors(edge->twin(), &rpolyline);
|
||||
polyline.points.insert(polyline.points.begin(), rpolyline.points.rbegin(), rpolyline.points.rend());
|
||||
polyline.width.insert(polyline.width.begin(), rpolyline.width.rbegin(), rpolyline.width.rend());
|
||||
polyline.endpoints.first = rpolyline.endpoints.second;
|
||||
}
|
||||
|
||||
assert(polyline.width.size() == polyline.points.size()*2 - 2);
|
||||
|
||||
// prevent loop endpoints from being extended
|
||||
if (polyline.first_point() == polyline.last_point()) {
|
||||
polyline.endpoints.first = false;
|
||||
polyline.endpoints.second = false;
|
||||
}
|
||||
|
||||
// append polyline to result
|
||||
polylines->push_back(polyline);
|
||||
}
|
||||
|
||||
#ifdef SLIC3R_DEBUG
|
||||
{
|
||||
static int iRun = 0;
|
||||
dump_voronoi_to_svg(this->lines, this->vd, polylines, debug_out_path("MedialAxis-%d.svg", iRun ++).c_str());
|
||||
printf("Thick lines: ");
|
||||
for (ThickPolylines::const_iterator it = polylines->begin(); it != polylines->end(); ++ it) {
|
||||
ThickLines lines = it->thicklines();
|
||||
for (ThickLines::const_iterator it2 = lines.begin(); it2 != lines.end(); ++ it2) {
|
||||
printf("%f,%f ", it2->a_width, it2->b_width);
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
#endif /* SLIC3R_DEBUG */
|
||||
}
|
||||
|
||||
void
|
||||
MedialAxis::build(Polylines* polylines)
|
||||
{
|
||||
ThickPolylines tp;
|
||||
this->build(&tp);
|
||||
polylines->insert(polylines->end(), tp.begin(), tp.end());
|
||||
}
|
||||
|
||||
void
|
||||
MedialAxis::process_edge_neighbors(const VD::edge_type* edge, ThickPolyline* polyline)
|
||||
{
|
||||
while (true) {
|
||||
// Since rot_next() works on the edge starting point but we want
|
||||
// to find neighbors on the ending point, we just swap edge with
|
||||
// its twin.
|
||||
const VD::edge_type* twin = edge->twin();
|
||||
|
||||
// count neighbors for this edge
|
||||
std::vector<const VD::edge_type*> neighbors;
|
||||
for (const VD::edge_type* neighbor = twin->rot_next(); neighbor != twin;
|
||||
neighbor = neighbor->rot_next()) {
|
||||
if (this->valid_edges.count(neighbor) > 0) neighbors.push_back(neighbor);
|
||||
}
|
||||
|
||||
// if we have a single neighbor then we can continue recursively
|
||||
if (neighbors.size() == 1) {
|
||||
const VD::edge_type* neighbor = neighbors.front();
|
||||
|
||||
// break if this is a closed loop
|
||||
if (this->edges.count(neighbor) == 0) return;
|
||||
|
||||
Point new_point(neighbor->vertex1()->x(), neighbor->vertex1()->y());
|
||||
polyline->points.push_back(new_point);
|
||||
polyline->width.push_back(this->thickness[neighbor].first);
|
||||
polyline->width.push_back(this->thickness[neighbor].second);
|
||||
(void)this->edges.erase(neighbor);
|
||||
(void)this->edges.erase(neighbor->twin());
|
||||
edge = neighbor;
|
||||
} else if (neighbors.size() == 0) {
|
||||
polyline->endpoints.second = true;
|
||||
return;
|
||||
} else {
|
||||
// T-shaped or star-shaped joint
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MedialAxis::validate_edge(const VD::edge_type* edge)
|
||||
{
|
||||
// prevent overflows and detect almost-infinite edges
|
||||
#ifndef CLIPPERLIB_INT32
|
||||
if (std::abs(edge->vertex0()->x()) > double(CLIPPER_MAX_COORD_UNSCALED) ||
|
||||
std::abs(edge->vertex0()->y()) > double(CLIPPER_MAX_COORD_UNSCALED) ||
|
||||
std::abs(edge->vertex1()->x()) > double(CLIPPER_MAX_COORD_UNSCALED) ||
|
||||
std::abs(edge->vertex1()->y()) > double(CLIPPER_MAX_COORD_UNSCALED))
|
||||
return false;
|
||||
#endif // CLIPPERLIB_INT32
|
||||
|
||||
// construct the line representing this edge of the Voronoi diagram
|
||||
const Line line(
|
||||
Point( edge->vertex0()->x(), edge->vertex0()->y() ),
|
||||
Point( edge->vertex1()->x(), edge->vertex1()->y() )
|
||||
);
|
||||
|
||||
// discard edge if it lies outside the supplied shape
|
||||
// this could maybe be optimized (checking inclusion of the endpoints
|
||||
// might give false positives as they might belong to the contour itself)
|
||||
if (this->expolygon != NULL) {
|
||||
if (line.a == line.b) {
|
||||
// in this case, contains(line) returns a false positive
|
||||
if (!this->expolygon->contains(line.a)) return false;
|
||||
} else {
|
||||
if (!this->expolygon->contains(line)) return false;
|
||||
}
|
||||
}
|
||||
|
||||
// retrieve the original line segments which generated the edge we're checking
|
||||
const VD::cell_type* cell_l = edge->cell();
|
||||
const VD::cell_type* cell_r = edge->twin()->cell();
|
||||
const Line &segment_l = this->retrieve_segment(cell_l);
|
||||
const Line &segment_r = this->retrieve_segment(cell_r);
|
||||
|
||||
/*
|
||||
SVG svg("edge.svg");
|
||||
svg.draw(*this->expolygon);
|
||||
svg.draw(line);
|
||||
svg.draw(segment_l, "red");
|
||||
svg.draw(segment_r, "blue");
|
||||
svg.Close();
|
||||
*/
|
||||
|
||||
/* Calculate thickness of the cross-section at both the endpoints of this edge.
|
||||
Our Voronoi edge is part of a CCW sequence going around its Voronoi cell
|
||||
located on the left side. (segment_l).
|
||||
This edge's twin goes around segment_r. Thus, segment_r is
|
||||
oriented in the same direction as our main edge, and segment_l is oriented
|
||||
in the same direction as our twin edge.
|
||||
We used to only consider the (half-)distances to segment_r, and that works
|
||||
whenever segment_l and segment_r are almost specular and facing. However,
|
||||
at curves they are staggered and they only face for a very little length
|
||||
(our very short edge represents such visibility).
|
||||
Both w0 and w1 can be calculated either towards cell_l or cell_r with equal
|
||||
results by Voronoi definition.
|
||||
When cell_l or cell_r don't refer to the segment but only to an endpoint, we
|
||||
calculate the distance to that endpoint instead. */
|
||||
|
||||
coordf_t w0 = cell_r->contains_segment()
|
||||
? segment_r.distance_to(line.a)*2
|
||||
: (this->retrieve_endpoint(cell_r) - line.a).cast<double>().norm()*2;
|
||||
|
||||
coordf_t w1 = cell_l->contains_segment()
|
||||
? segment_l.distance_to(line.b)*2
|
||||
: (this->retrieve_endpoint(cell_l) - line.b).cast<double>().norm()*2;
|
||||
|
||||
if (cell_l->contains_segment() && cell_r->contains_segment()) {
|
||||
// calculate the relative angle between the two boundary segments
|
||||
double angle = fabs(segment_r.orientation() - segment_l.orientation());
|
||||
if (angle > PI) angle = 2*PI - angle;
|
||||
assert(angle >= 0 && angle <= PI);
|
||||
|
||||
// fabs(angle) ranges from 0 (collinear, same direction) to PI (collinear, opposite direction)
|
||||
// we're interested only in segments close to the second case (facing segments)
|
||||
// so we allow some tolerance.
|
||||
// this filter ensures that we're dealing with a narrow/oriented area (longer than thick)
|
||||
// we don't run it on edges not generated by two segments (thus generated by one segment
|
||||
// and the endpoint of another segment), since their orientation would not be meaningful
|
||||
if (PI - angle > PI/8) {
|
||||
// angle is not narrow enough
|
||||
|
||||
// only apply this filter to segments that are not too short otherwise their
|
||||
// angle could possibly be not meaningful
|
||||
if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON || line.length() >= this->min_width)
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (w0 < this->min_width && w1 < this->min_width)
|
||||
return false;
|
||||
|
||||
if (w0 > this->max_width && w1 > this->max_width)
|
||||
return false;
|
||||
|
||||
this->thickness[edge] = std::make_pair(w0, w1);
|
||||
this->thickness[edge->twin()] = std::make_pair(w1, w0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const Line& MedialAxis::retrieve_segment(const VD::cell_type* cell) const
|
||||
{
|
||||
return this->lines[cell->source_index()];
|
||||
}
|
||||
|
||||
const Point& MedialAxis::retrieve_endpoint(const VD::cell_type* cell) const
|
||||
{
|
||||
const Line& line = this->retrieve_segment(cell);
|
||||
if (cell->source_category() == SOURCE_CATEGORY_SEGMENT_START_POINT) {
|
||||
return line.a;
|
||||
} else {
|
||||
return line.b;
|
||||
}
|
||||
}
|
||||
|
||||
void assemble_transform(Transform3d& transform, const Vec3d& translation, const Vec3d& rotation, const Vec3d& scale, const Vec3d& mirror)
|
||||
{
|
||||
transform = Transform3d::Identity();
|
||||
|
|
|
@ -10,18 +10,6 @@
|
|||
// Serialization through the Cereal library
|
||||
#include <cereal/access.hpp>
|
||||
|
||||
#define BOOST_VORONOI_USE_GMP 1
|
||||
|
||||
#ifdef _MSC_VER
|
||||
// Suppress warning C4146 in OpenVDB: unary minus operator applied to unsigned type, result still unsigned
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4146)
|
||||
#endif // _MSC_VER
|
||||
#include "boost/polygon/voronoi.hpp"
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif // _MSC_VER
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace ClipperLib {
|
||||
|
@ -47,7 +35,7 @@ enum Orientation
|
|||
// and d is limited to 63 bits + signum and we are good.
|
||||
static inline Orientation orient(const Point &a, const Point &b, const Point &c)
|
||||
{
|
||||
// BOOST_STATIC_ASSERT(sizeof(coord_t) * 2 == sizeof(int64_t));
|
||||
static_assert(sizeof(coord_t) * 2 == sizeof(int64_t), "orient works with 32 bit coordinates");
|
||||
int64_t u = int64_t(b(0)) * int64_t(c(1)) - int64_t(b(1)) * int64_t(c(0));
|
||||
int64_t v = int64_t(a(0)) * int64_t(c(1)) - int64_t(a(1)) * int64_t(c(0));
|
||||
int64_t w = int64_t(a(0)) * int64_t(b(1)) - int64_t(a(1)) * int64_t(b(0));
|
||||
|
@ -325,38 +313,6 @@ bool liang_barsky_line_clipping(
|
|||
return liang_barsky_line_clipping(x0clip, x1clip, bbox);
|
||||
}
|
||||
|
||||
// Ugly named variant, that accepts the squared line
|
||||
// Don't call me with a nearly zero length vector!
|
||||
// sympy:
|
||||
// factor(solve([a * x + b * y + c, x**2 + y**2 - r**2], [x, y])[0])
|
||||
// factor(solve([a * x + b * y + c, x**2 + y**2 - r**2], [x, y])[1])
|
||||
template<typename T>
|
||||
int ray_circle_intersections_r2_lv2_c(T r2, T a, T b, T lv2, T c, std::pair<Eigen::Matrix<T, 2, 1, Eigen::DontAlign>, Eigen::Matrix<T, 2, 1, Eigen::DontAlign>> &out)
|
||||
{
|
||||
T x0 = - a * c;
|
||||
T y0 = - b * c;
|
||||
T d2 = r2 * lv2 - c * c;
|
||||
if (d2 < T(0))
|
||||
return 0;
|
||||
T d = sqrt(d2);
|
||||
out.first.x() = (x0 + b * d) / lv2;
|
||||
out.first.y() = (y0 - a * d) / lv2;
|
||||
out.second.x() = (x0 - b * d) / lv2;
|
||||
out.second.y() = (y0 + a * d) / lv2;
|
||||
return d == T(0) ? 1 : 2;
|
||||
}
|
||||
template<typename T>
|
||||
int ray_circle_intersections(T r, T a, T b, T c, std::pair<Eigen::Matrix<T, 2, 1, Eigen::DontAlign>, Eigen::Matrix<T, 2, 1, Eigen::DontAlign>> &out)
|
||||
{
|
||||
T lv2 = a * a + b * b;
|
||||
if (lv2 < T(SCALED_EPSILON * SCALED_EPSILON)) {
|
||||
//FIXME what is the correct epsilon?
|
||||
// What if the line touches the circle?
|
||||
return false;
|
||||
}
|
||||
return ray_circle_intersections_r2_lv2_c2(r * r, a, b, a * a + b * b, c, out);
|
||||
}
|
||||
|
||||
Pointf3s convex_hull(Pointf3s points);
|
||||
Polygon convex_hull(Points points);
|
||||
Polygon convex_hull(const Polygons &polygons);
|
||||
|
@ -384,14 +340,6 @@ template<typename T> T angle_to_0_2PI(T angle)
|
|||
return angle;
|
||||
}
|
||||
|
||||
/// Find the center of the circle corresponding to the vector of Points as an arc.
|
||||
Point circle_center_taubin_newton(const Points::const_iterator& input_start, const Points::const_iterator& input_end, size_t cycles = 20);
|
||||
inline Point circle_center_taubin_newton(const Points& input, size_t cycles = 20) { return circle_center_taubin_newton(input.cbegin(), input.cend(), cycles); }
|
||||
|
||||
/// Find the center of the circle corresponding to the vector of Pointfs as an arc.
|
||||
Vec2d circle_center_taubin_newton(const Vec2ds::const_iterator& input_start, const Vec2ds::const_iterator& input_end, size_t cycles = 20);
|
||||
inline Vec2d circle_center_taubin_newton(const Vec2ds& input, size_t cycles = 20) { return circle_center_taubin_newton(input.cbegin(), input.cend(), cycles); }
|
||||
|
||||
void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval);
|
||||
|
||||
double linint(double value, double oldmin, double oldmax, double newmin, double newmax);
|
||||
|
@ -401,36 +349,6 @@ bool arrange(
|
|||
// output
|
||||
Pointfs &positions);
|
||||
|
||||
class VoronoiDiagram : public boost::polygon::voronoi_diagram<double> {
|
||||
public:
|
||||
typedef double coord_type;
|
||||
typedef boost::polygon::point_data<coordinate_type> point_type;
|
||||
typedef boost::polygon::segment_data<coordinate_type> segment_type;
|
||||
typedef boost::polygon::rectangle_data<coordinate_type> rect_type;
|
||||
};
|
||||
|
||||
class MedialAxis {
|
||||
public:
|
||||
Lines lines;
|
||||
const ExPolygon* expolygon;
|
||||
double max_width;
|
||||
double min_width;
|
||||
MedialAxis(double _max_width, double _min_width, const ExPolygon* _expolygon = NULL)
|
||||
: expolygon(_expolygon), max_width(_max_width), min_width(_min_width) {};
|
||||
void build(ThickPolylines* polylines);
|
||||
void build(Polylines* polylines);
|
||||
|
||||
private:
|
||||
using VD = VoronoiDiagram;
|
||||
VD vd;
|
||||
std::set<const VD::edge_type*> edges, valid_edges;
|
||||
std::map<const VD::edge_type*, std::pair<coordf_t,coordf_t> > thickness;
|
||||
void process_edge_neighbors(const VD::edge_type* edge, ThickPolyline* polyline);
|
||||
bool validate_edge(const VD::edge_type* edge);
|
||||
const Line& retrieve_segment(const VD::cell_type* cell) const;
|
||||
const Point& retrieve_endpoint(const VD::cell_type* cell) const;
|
||||
};
|
||||
|
||||
// Sets the given transform by assembling the given transformations in the following order:
|
||||
// 1) mirror
|
||||
// 2) scale
|
||||
|
|
97
src/libslic3r/Geometry/Circle.cpp
Normal file
97
src/libslic3r/Geometry/Circle.cpp
Normal file
|
@ -0,0 +1,97 @@
|
|||
#include "Circle.hpp"
|
||||
|
||||
#include "../Polygon.hpp"
|
||||
|
||||
#include <numeric>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
namespace Slic3r { namespace Geometry {
|
||||
|
||||
Point circle_center_taubin_newton(const Points::const_iterator& input_begin, const Points::const_iterator& input_end, size_t cycles)
|
||||
{
|
||||
Vec2ds tmp;
|
||||
tmp.reserve(std::distance(input_begin, input_end));
|
||||
std::transform(input_begin, input_end, std::back_inserter(tmp), [] (const Point& in) { return unscale(in); } );
|
||||
Vec2d center = circle_center_taubin_newton(tmp.cbegin(), tmp.end(), cycles);
|
||||
return Point::new_scale(center.x(), center.y());
|
||||
}
|
||||
|
||||
/// Adapted from work in "Circular and Linear Regression: Fitting circles and lines by least squares", pg 126
|
||||
/// Returns a point corresponding to the center of a circle for which all of the points from input_begin to input_end
|
||||
/// lie on.
|
||||
Vec2d circle_center_taubin_newton(const Vec2ds::const_iterator& input_begin, const Vec2ds::const_iterator& input_end, size_t cycles)
|
||||
{
|
||||
// calculate the centroid of the data set
|
||||
const Vec2d sum = std::accumulate(input_begin, input_end, Vec2d(0,0));
|
||||
const size_t n = std::distance(input_begin, input_end);
|
||||
const double n_flt = static_cast<double>(n);
|
||||
const Vec2d centroid { sum / n_flt };
|
||||
|
||||
// Compute the normalized moments of the data set.
|
||||
double Mxx = 0, Myy = 0, Mxy = 0, Mxz = 0, Myz = 0, Mzz = 0;
|
||||
for (auto it = input_begin; it < input_end; ++it) {
|
||||
// center/normalize the data.
|
||||
double Xi {it->x() - centroid.x()};
|
||||
double Yi {it->y() - centroid.y()};
|
||||
double Zi {Xi*Xi + Yi*Yi};
|
||||
Mxy += (Xi*Yi);
|
||||
Mxx += (Xi*Xi);
|
||||
Myy += (Yi*Yi);
|
||||
Mxz += (Xi*Zi);
|
||||
Myz += (Yi*Zi);
|
||||
Mzz += (Zi*Zi);
|
||||
}
|
||||
|
||||
// divide by number of points to get the moments
|
||||
Mxx /= n_flt;
|
||||
Myy /= n_flt;
|
||||
Mxy /= n_flt;
|
||||
Mxz /= n_flt;
|
||||
Myz /= n_flt;
|
||||
Mzz /= n_flt;
|
||||
|
||||
// Compute the coefficients of the characteristic polynomial for the circle
|
||||
// eq 5.60
|
||||
const double Mz {Mxx + Myy}; // xx + yy = z
|
||||
const double Cov_xy {Mxx*Myy - Mxy*Mxy}; // this shows up a couple times so cache it here.
|
||||
const double C3 {4.0*Mz};
|
||||
const double C2 {-3.0*(Mz*Mz) - Mzz};
|
||||
const double C1 {Mz*(Mzz - (Mz*Mz)) + 4.0*Mz*Cov_xy - (Mxz*Mxz) - (Myz*Myz)};
|
||||
const double C0 {(Mxz*Mxz)*Myy + (Myz*Myz)*Mxx - 2.0*Mxz*Myz*Mxy - Cov_xy*(Mzz - (Mz*Mz))};
|
||||
|
||||
const double C22 = {C2 + C2};
|
||||
const double C33 = {C3 + C3 + C3};
|
||||
|
||||
// solve the characteristic polynomial with Newton's method.
|
||||
double xnew = 0.0;
|
||||
double ynew = 1e20;
|
||||
|
||||
for (size_t i = 0; i < cycles; ++i) {
|
||||
const double yold {ynew};
|
||||
ynew = C0 + xnew * (C1 + xnew*(C2 + xnew * C3));
|
||||
if (std::abs(ynew) > std::abs(yold)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Geometry: Fit is going in the wrong direction.\n";
|
||||
return Vec2d(std::nan(""), std::nan(""));
|
||||
}
|
||||
const double Dy {C1 + xnew*(C22 + xnew*C33)};
|
||||
|
||||
const double xold {xnew};
|
||||
xnew = xold - (ynew / Dy);
|
||||
|
||||
if (std::abs((xnew-xold) / xnew) < 1e-12) i = cycles; // converged, we're done here
|
||||
|
||||
if (xnew < 0) {
|
||||
// reset, we went negative
|
||||
xnew = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
// compute the determinant and the circle's parameters now that we've solved.
|
||||
double DET = xnew*xnew - xnew*Mz + Cov_xy;
|
||||
|
||||
Vec2d center(Mxz * (Myy - xnew) - Myz * Mxy, Myz * (Mxx - xnew) - Mxz*Mxy);
|
||||
center /= (DET * 2.);
|
||||
return center + centroid;
|
||||
}
|
||||
|
||||
} } // namespace Slic3r::Geometry
|
183
src/libslic3r/Geometry/Circle.hpp
Normal file
183
src/libslic3r/Geometry/Circle.hpp
Normal file
|
@ -0,0 +1,183 @@
|
|||
#ifndef slic3r_Geometry_Circle_hpp_
|
||||
#define slic3r_Geometry_Circle_hpp_
|
||||
|
||||
#include "../Point.hpp"
|
||||
|
||||
#include <Eigen/Geometry>
|
||||
|
||||
namespace Slic3r { namespace Geometry {
|
||||
|
||||
/// Find the center of the circle corresponding to the vector of Points as an arc.
|
||||
Point circle_center_taubin_newton(const Points::const_iterator& input_start, const Points::const_iterator& input_end, size_t cycles = 20);
|
||||
inline Point circle_center_taubin_newton(const Points& input, size_t cycles = 20) { return circle_center_taubin_newton(input.cbegin(), input.cend(), cycles); }
|
||||
|
||||
/// Find the center of the circle corresponding to the vector of Pointfs as an arc.
|
||||
Vec2d circle_center_taubin_newton(const Vec2ds::const_iterator& input_start, const Vec2ds::const_iterator& input_end, size_t cycles = 20);
|
||||
inline Vec2d circle_center_taubin_newton(const Vec2ds& input, size_t cycles = 20) { return circle_center_taubin_newton(input.cbegin(), input.cend(), cycles); }
|
||||
|
||||
// https://en.wikipedia.org/wiki/Circumscribed_circle
|
||||
// Circumcenter coordinates, Cartesian coordinates
|
||||
template<typename Vector>
|
||||
Vector circle_center(const Vector &a, const Vector &bsrc, const Vector &csrc, typename Vector::Scalar epsilon)
|
||||
{
|
||||
using Scalar = typename Vector::Scalar;
|
||||
Vector b = bsrc - a;
|
||||
Vector c = csrc - a;
|
||||
Scalar lb = b.squaredNorm();
|
||||
Scalar lc = c.squaredNorm();
|
||||
if (Scalar d = b.x() * c.y() - b.y() * c.x(); std::abs(d) < epsilon) {
|
||||
// The three points are collinear. Take the center of the two points
|
||||
// furthest away from each other.
|
||||
Scalar lbc = (csrc - bsrc).squaredNorm();
|
||||
return Scalar(0.5) * (
|
||||
lb > lc && lb > lbc ? a + bsrc :
|
||||
lc > lb && lc > lbc ? a + csrc : bsrc + csrc);
|
||||
} else {
|
||||
Vector v = lc * b - lb * c;
|
||||
return a + Vector(- v.y(), v.x()) / (2 * d);
|
||||
}
|
||||
}
|
||||
|
||||
// 2D circle defined by its center and squared radius
|
||||
template<typename Vector>
|
||||
struct CircleSq {
|
||||
using Scalar = typename Vector::Scalar;
|
||||
|
||||
Vector center;
|
||||
Scalar radius2;
|
||||
|
||||
CircleSq() {}
|
||||
CircleSq(const Vector ¢er, const Scalar radius2) : center(center), radius2(radius2) {}
|
||||
CircleSq(const Vector &a, const Vector &b) : center(Scalar(0.5) * (a + b)) { radius2 = (a - center).squaredNorm(); }
|
||||
CircleSq(const Vector &a, const Vector &b, const Vector &c, Scalar epsilon) {
|
||||
this->center = circle_center(a, b, c, epsilon);
|
||||
this->radius2 = (a - this->center).squaredNorm();
|
||||
}
|
||||
|
||||
bool invalid() const { return this->radius2 < 0; }
|
||||
bool valid() const { return ! this->invalid(); }
|
||||
bool contains(const Vector &p) const { return (p - this->center).squaredNorm() < this->radius2; }
|
||||
bool contains(const Vector &p, const Scalar epsilon2) const { return (p - this->center).squaredNorm() < this->radius2 + epsilon2; }
|
||||
|
||||
CircleSq inflated(Scalar epsilon) const
|
||||
{ assert(this->radius2 >= 0); Scalar r = sqrt(this->radius2) + epsilon; return { this->center, r * r }; }
|
||||
|
||||
static CircleSq make_invalid() { return CircleSq { { 0, 0 }, -1 }; }
|
||||
};
|
||||
|
||||
// 2D circle defined by its center and radius
|
||||
template<typename Vector>
|
||||
struct Circle {
|
||||
using Scalar = typename Vector::Scalar;
|
||||
|
||||
Vector center;
|
||||
Scalar radius;
|
||||
|
||||
Circle() {}
|
||||
Circle(const Vector ¢er, const Scalar radius) : center(center), radius(radius) {}
|
||||
Circle(const Vector &a, const Vector &b) : center(Scalar(0.5) * (a + b)) { radius = (a - center).norm(); }
|
||||
Circle(const Vector &a, const Vector &b, const Vector &c, const Scalar epsilon) { *this = CircleSq(a, b, c, epsilon); }
|
||||
|
||||
// Conversion from CircleSq
|
||||
template<typename Vector2>
|
||||
explicit Circle(const CircleSq<Vector2> &c) : center(c.center), radius(c.radius2 <= 0 ? c.radius2 : sqrt(c.radius2)) {}
|
||||
template<typename Vector2>
|
||||
Circle operator=(const CircleSq<Vector2>& c) { this->center = c.center; this->radius = c.radius2 <= 0 ? c.radius2 : sqrt(c.radius2); }
|
||||
|
||||
bool invalid() const { return this->radius < 0; }
|
||||
bool valid() const { return ! this->invalid(); }
|
||||
bool contains(const Vector &p) const { return (p - this->center).squaredNorm() <= this->radius * this->radius; }
|
||||
bool contains(const Vector &p, const Scalar epsilon) const
|
||||
{ Scalar re = this->radius + epsilon; return (p - this->center).squaredNorm() < re * re; }
|
||||
|
||||
Circle inflated(Scalar epsilon) const { assert(this->radius >= 0); return { this->center, this->radius + epsilon }; }
|
||||
|
||||
static Circle make_invalid() { return Circle { { 0, 0 }, -1 }; }
|
||||
};
|
||||
|
||||
using Circlef = Circle<Vec2f>;
|
||||
using Circled = Circle<Vec2d>;
|
||||
using CircleSqf = CircleSq<Vec2f>;
|
||||
using CircleSqd = CircleSq<Vec2d>;
|
||||
|
||||
// Randomized algorithm by Emo Welzl, working with squared radii for efficiency. The returned circle radius is inflated by epsilon.
|
||||
template<typename Vector, typename Points>
|
||||
CircleSq<Vector> smallest_enclosing_circle2_welzl(const Points &points, const typename Vector::Scalar epsilon)
|
||||
{
|
||||
using Scalar = typename Vector::Scalar;
|
||||
CircleSq<Vector> circle;
|
||||
|
||||
if (! points.empty()) {
|
||||
const auto &p0 = points[0].template cast<Scalar>();
|
||||
if (points.size() == 1) {
|
||||
circle.center = p0;
|
||||
circle.radius2 = epsilon * epsilon;
|
||||
} else {
|
||||
circle = CircleSq<Vector>(p0, points[1].template cast<Scalar>()).inflated(epsilon);
|
||||
for (size_t i = 2; i < points.size(); ++ i)
|
||||
if (const Vector &p = points[i].template cast<Scalar>(); ! circle.contains(p)) {
|
||||
// p is the first point on the smallest circle enclosing points[0..i]
|
||||
circle = CircleSq<Vector>(p0, p).inflated(epsilon);
|
||||
for (size_t j = 1; j < i; ++ j)
|
||||
if (const Vector &q = points[j].template cast<Scalar>(); ! circle.contains(q)) {
|
||||
// q is the second point on the smallest circle enclosing points[0..i]
|
||||
circle = CircleSq<Vector>(p, q).inflated(epsilon);
|
||||
for (size_t k = 0; k < j; ++ k)
|
||||
if (const Vector &r = points[k].template cast<Scalar>(); ! circle.contains(r))
|
||||
circle = CircleSq<Vector>(p, q, r, epsilon).inflated(epsilon);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return circle;
|
||||
}
|
||||
|
||||
// Randomized algorithm by Emo Welzl. The returned circle radius is inflated by epsilon.
|
||||
template<typename Vector, typename Points>
|
||||
Circle<Vector> smallest_enclosing_circle_welzl(const Points &points, const typename Vector::Scalar epsilon)
|
||||
{
|
||||
return Circle<Vector>(smallest_enclosing_circle2_welzl<Vector, Points>(points, epsilon));
|
||||
}
|
||||
|
||||
// Randomized algorithm by Emo Welzl. The returned circle radius is inflated by SCALED_EPSILON.
|
||||
inline Circled smallest_enclosing_circle_welzl(const Points &points)
|
||||
{
|
||||
return smallest_enclosing_circle_welzl<Vec2d, Points>(points, SCALED_EPSILON);
|
||||
}
|
||||
|
||||
// Ugly named variant, that accepts the squared line
|
||||
// Don't call me with a nearly zero length vector!
|
||||
// sympy:
|
||||
// factor(solve([a * x + b * y + c, x**2 + y**2 - r**2], [x, y])[0])
|
||||
// factor(solve([a * x + b * y + c, x**2 + y**2 - r**2], [x, y])[1])
|
||||
template<typename T>
|
||||
int ray_circle_intersections_r2_lv2_c(T r2, T a, T b, T lv2, T c, std::pair<Eigen::Matrix<T, 2, 1, Eigen::DontAlign>, Eigen::Matrix<T, 2, 1, Eigen::DontAlign>> &out)
|
||||
{
|
||||
T x0 = - a * c;
|
||||
T y0 = - b * c;
|
||||
T d2 = r2 * lv2 - c * c;
|
||||
if (d2 < T(0))
|
||||
return 0;
|
||||
T d = sqrt(d2);
|
||||
out.first.x() = (x0 + b * d) / lv2;
|
||||
out.first.y() = (y0 - a * d) / lv2;
|
||||
out.second.x() = (x0 - b * d) / lv2;
|
||||
out.second.y() = (y0 + a * d) / lv2;
|
||||
return d == T(0) ? 1 : 2;
|
||||
}
|
||||
template<typename T>
|
||||
int ray_circle_intersections(T r, T a, T b, T c, std::pair<Eigen::Matrix<T, 2, 1, Eigen::DontAlign>, Eigen::Matrix<T, 2, 1, Eigen::DontAlign>> &out)
|
||||
{
|
||||
T lv2 = a * a + b * b;
|
||||
if (lv2 < T(SCALED_EPSILON * SCALED_EPSILON)) {
|
||||
//FIXME what is the correct epsilon?
|
||||
// What if the line touches the circle?
|
||||
return false;
|
||||
}
|
||||
return ray_circle_intersections_r2_lv2_c2(r * r, a, b, a * a + b * b, c, out);
|
||||
}
|
||||
|
||||
} } // namespace Slic3r::Geometry
|
||||
|
||||
#endif // slic3r_Geometry_Circle_hpp_
|
712
src/libslic3r/Geometry/MedialAxis.cpp
Normal file
712
src/libslic3r/Geometry/MedialAxis.cpp
Normal file
|
@ -0,0 +1,712 @@
|
|||
#include "MedialAxis.hpp"
|
||||
|
||||
#include "clipper.hpp"
|
||||
|
||||
#ifdef SLIC3R_DEBUG
|
||||
namespace boost { namespace polygon {
|
||||
|
||||
// The following code for the visualization of the boost Voronoi diagram is based on:
|
||||
//
|
||||
// Boost.Polygon library voronoi_graphic_utils.hpp header file
|
||||
// Copyright Andrii Sydorchuk 2010-2012.
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE_1_0.txt or copy at
|
||||
// http://www.boost.org/LICENSE_1_0.txt)
|
||||
template <typename CT>
|
||||
class voronoi_visual_utils {
|
||||
public:
|
||||
// Discretize parabolic Voronoi edge.
|
||||
// Parabolic Voronoi edges are always formed by one point and one segment
|
||||
// from the initial input set.
|
||||
//
|
||||
// Args:
|
||||
// point: input point.
|
||||
// segment: input segment.
|
||||
// max_dist: maximum discretization distance.
|
||||
// discretization: point discretization of the given Voronoi edge.
|
||||
//
|
||||
// Template arguments:
|
||||
// InCT: coordinate type of the input geometries (usually integer).
|
||||
// Point: point type, should model point concept.
|
||||
// Segment: segment type, should model segment concept.
|
||||
//
|
||||
// Important:
|
||||
// discretization should contain both edge endpoints initially.
|
||||
template <class InCT1, class InCT2,
|
||||
template<class> class Point,
|
||||
template<class> class Segment>
|
||||
static
|
||||
typename enable_if<
|
||||
typename gtl_and<
|
||||
typename gtl_if<
|
||||
typename is_point_concept<
|
||||
typename geometry_concept< Point<InCT1> >::type
|
||||
>::type
|
||||
>::type,
|
||||
typename gtl_if<
|
||||
typename is_segment_concept<
|
||||
typename geometry_concept< Segment<InCT2> >::type
|
||||
>::type
|
||||
>::type
|
||||
>::type,
|
||||
void
|
||||
>::type discretize(
|
||||
const Point<InCT1>& point,
|
||||
const Segment<InCT2>& segment,
|
||||
const CT max_dist,
|
||||
std::vector< Point<CT> >* discretization) {
|
||||
// Apply the linear transformation to move start point of the segment to
|
||||
// the point with coordinates (0, 0) and the direction of the segment to
|
||||
// coincide the positive direction of the x-axis.
|
||||
CT segm_vec_x = cast(x(high(segment))) - cast(x(low(segment)));
|
||||
CT segm_vec_y = cast(y(high(segment))) - cast(y(low(segment)));
|
||||
CT sqr_segment_length = segm_vec_x * segm_vec_x + segm_vec_y * segm_vec_y;
|
||||
|
||||
// Compute x-coordinates of the endpoints of the edge
|
||||
// in the transformed space.
|
||||
CT projection_start = sqr_segment_length *
|
||||
get_point_projection((*discretization)[0], segment);
|
||||
CT projection_end = sqr_segment_length *
|
||||
get_point_projection((*discretization)[1], segment);
|
||||
|
||||
// Compute parabola parameters in the transformed space.
|
||||
// Parabola has next representation:
|
||||
// f(x) = ((x-rot_x)^2 + rot_y^2) / (2.0*rot_y).
|
||||
CT point_vec_x = cast(x(point)) - cast(x(low(segment)));
|
||||
CT point_vec_y = cast(y(point)) - cast(y(low(segment)));
|
||||
CT rot_x = segm_vec_x * point_vec_x + segm_vec_y * point_vec_y;
|
||||
CT rot_y = segm_vec_x * point_vec_y - segm_vec_y * point_vec_x;
|
||||
|
||||
// Save the last point.
|
||||
Point<CT> last_point = (*discretization)[1];
|
||||
discretization->pop_back();
|
||||
|
||||
// Use stack to avoid recursion.
|
||||
std::stack<CT> point_stack;
|
||||
point_stack.push(projection_end);
|
||||
CT cur_x = projection_start;
|
||||
CT cur_y = parabola_y(cur_x, rot_x, rot_y);
|
||||
|
||||
// Adjust max_dist parameter in the transformed space.
|
||||
const CT max_dist_transformed = max_dist * max_dist * sqr_segment_length;
|
||||
while (!point_stack.empty()) {
|
||||
CT new_x = point_stack.top();
|
||||
CT new_y = parabola_y(new_x, rot_x, rot_y);
|
||||
|
||||
// Compute coordinates of the point of the parabola that is
|
||||
// furthest from the current line segment.
|
||||
CT mid_x = (new_y - cur_y) / (new_x - cur_x) * rot_y + rot_x;
|
||||
CT mid_y = parabola_y(mid_x, rot_x, rot_y);
|
||||
|
||||
// Compute maximum distance between the given parabolic arc
|
||||
// and line segment that discretize it.
|
||||
CT dist = (new_y - cur_y) * (mid_x - cur_x) -
|
||||
(new_x - cur_x) * (mid_y - cur_y);
|
||||
dist = dist * dist / ((new_y - cur_y) * (new_y - cur_y) +
|
||||
(new_x - cur_x) * (new_x - cur_x));
|
||||
if (dist <= max_dist_transformed) {
|
||||
// Distance between parabola and line segment is less than max_dist.
|
||||
point_stack.pop();
|
||||
CT inter_x = (segm_vec_x * new_x - segm_vec_y * new_y) /
|
||||
sqr_segment_length + cast(x(low(segment)));
|
||||
CT inter_y = (segm_vec_x * new_y + segm_vec_y * new_x) /
|
||||
sqr_segment_length + cast(y(low(segment)));
|
||||
discretization->push_back(Point<CT>(inter_x, inter_y));
|
||||
cur_x = new_x;
|
||||
cur_y = new_y;
|
||||
} else {
|
||||
point_stack.push(mid_x);
|
||||
}
|
||||
}
|
||||
|
||||
// Update last point.
|
||||
discretization->back() = last_point;
|
||||
}
|
||||
|
||||
private:
|
||||
// Compute y(x) = ((x - a) * (x - a) + b * b) / (2 * b).
|
||||
static CT parabola_y(CT x, CT a, CT b) {
|
||||
return ((x - a) * (x - a) + b * b) / (b + b);
|
||||
}
|
||||
|
||||
// Get normalized length of the distance between:
|
||||
// 1) point projection onto the segment
|
||||
// 2) start point of the segment
|
||||
// Return this length divided by the segment length. This is made to avoid
|
||||
// sqrt computation during transformation from the initial space to the
|
||||
// transformed one and vice versa. The assumption is made that projection of
|
||||
// the point lies between the start-point and endpoint of the segment.
|
||||
template <class InCT,
|
||||
template<class> class Point,
|
||||
template<class> class Segment>
|
||||
static
|
||||
typename enable_if<
|
||||
typename gtl_and<
|
||||
typename gtl_if<
|
||||
typename is_point_concept<
|
||||
typename geometry_concept< Point<int> >::type
|
||||
>::type
|
||||
>::type,
|
||||
typename gtl_if<
|
||||
typename is_segment_concept<
|
||||
typename geometry_concept< Segment<long> >::type
|
||||
>::type
|
||||
>::type
|
||||
>::type,
|
||||
CT
|
||||
>::type get_point_projection(
|
||||
const Point<CT>& point, const Segment<InCT>& segment) {
|
||||
CT segment_vec_x = cast(x(high(segment))) - cast(x(low(segment)));
|
||||
CT segment_vec_y = cast(y(high(segment))) - cast(y(low(segment)));
|
||||
CT point_vec_x = x(point) - cast(x(low(segment)));
|
||||
CT point_vec_y = y(point) - cast(y(low(segment)));
|
||||
CT sqr_segment_length =
|
||||
segment_vec_x * segment_vec_x + segment_vec_y * segment_vec_y;
|
||||
CT vec_dot = segment_vec_x * point_vec_x + segment_vec_y * point_vec_y;
|
||||
return vec_dot / sqr_segment_length;
|
||||
}
|
||||
|
||||
template <typename InCT>
|
||||
static CT cast(const InCT& value) {
|
||||
return static_cast<CT>(value);
|
||||
}
|
||||
};
|
||||
|
||||
} } // namespace boost::polygon
|
||||
#endif // SLIC3R_DEBUG
|
||||
|
||||
namespace Slic3r { namespace Geometry {
|
||||
|
||||
|
||||
#ifdef SLIC3R_DEBUG
|
||||
// The following code for the visualization of the boost Voronoi diagram is based on:
|
||||
//
|
||||
// Boost.Polygon library voronoi_visualizer.cpp file
|
||||
// Copyright Andrii Sydorchuk 2010-2012.
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE_1_0.txt or copy at
|
||||
// http://www.boost.org/LICENSE_1_0.txt)
|
||||
namespace Voronoi { namespace Internal {
|
||||
|
||||
typedef double coordinate_type;
|
||||
typedef boost::polygon::point_data<coordinate_type> point_type;
|
||||
typedef boost::polygon::segment_data<coordinate_type> segment_type;
|
||||
typedef boost::polygon::rectangle_data<coordinate_type> rect_type;
|
||||
typedef boost::polygon::voronoi_diagram<coordinate_type> VD;
|
||||
typedef VD::cell_type cell_type;
|
||||
typedef VD::cell_type::source_index_type source_index_type;
|
||||
typedef VD::cell_type::source_category_type source_category_type;
|
||||
typedef VD::edge_type edge_type;
|
||||
typedef VD::cell_container_type cell_container_type;
|
||||
typedef VD::cell_container_type vertex_container_type;
|
||||
typedef VD::edge_container_type edge_container_type;
|
||||
typedef VD::const_cell_iterator const_cell_iterator;
|
||||
typedef VD::const_vertex_iterator const_vertex_iterator;
|
||||
typedef VD::const_edge_iterator const_edge_iterator;
|
||||
|
||||
static const std::size_t EXTERNAL_COLOR = 1;
|
||||
|
||||
inline void color_exterior(const VD::edge_type* edge)
|
||||
{
|
||||
if (edge->color() == EXTERNAL_COLOR)
|
||||
return;
|
||||
edge->color(EXTERNAL_COLOR);
|
||||
edge->twin()->color(EXTERNAL_COLOR);
|
||||
const VD::vertex_type* v = edge->vertex1();
|
||||
if (v == NULL || !edge->is_primary())
|
||||
return;
|
||||
v->color(EXTERNAL_COLOR);
|
||||
const VD::edge_type* e = v->incident_edge();
|
||||
do {
|
||||
color_exterior(e);
|
||||
e = e->rot_next();
|
||||
} while (e != v->incident_edge());
|
||||
}
|
||||
|
||||
inline point_type retrieve_point(const std::vector<segment_type> &segments, const cell_type& cell)
|
||||
{
|
||||
assert(cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT || cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT);
|
||||
return (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? low(segments[cell.source_index()]) : high(segments[cell.source_index()]);
|
||||
}
|
||||
|
||||
inline void clip_infinite_edge(const std::vector<segment_type> &segments, const edge_type& edge, coordinate_type bbox_max_size, std::vector<point_type>* clipped_edge)
|
||||
{
|
||||
const cell_type& cell1 = *edge.cell();
|
||||
const cell_type& cell2 = *edge.twin()->cell();
|
||||
point_type origin, direction;
|
||||
// Infinite edges could not be created by two segment sites.
|
||||
if (cell1.contains_point() && cell2.contains_point()) {
|
||||
point_type p1 = retrieve_point(segments, cell1);
|
||||
point_type p2 = retrieve_point(segments, cell2);
|
||||
origin.x((p1.x() + p2.x()) * 0.5);
|
||||
origin.y((p1.y() + p2.y()) * 0.5);
|
||||
direction.x(p1.y() - p2.y());
|
||||
direction.y(p2.x() - p1.x());
|
||||
} else {
|
||||
origin = cell1.contains_segment() ? retrieve_point(segments, cell2) : retrieve_point(segments, cell1);
|
||||
segment_type segment = cell1.contains_segment() ? segments[cell1.source_index()] : segments[cell2.source_index()];
|
||||
coordinate_type dx = high(segment).x() - low(segment).x();
|
||||
coordinate_type dy = high(segment).y() - low(segment).y();
|
||||
if ((low(segment) == origin) ^ cell1.contains_point()) {
|
||||
direction.x(dy);
|
||||
direction.y(-dx);
|
||||
} else {
|
||||
direction.x(-dy);
|
||||
direction.y(dx);
|
||||
}
|
||||
}
|
||||
coordinate_type koef = bbox_max_size / (std::max)(fabs(direction.x()), fabs(direction.y()));
|
||||
if (edge.vertex0() == NULL) {
|
||||
clipped_edge->push_back(point_type(
|
||||
origin.x() - direction.x() * koef,
|
||||
origin.y() - direction.y() * koef));
|
||||
} else {
|
||||
clipped_edge->push_back(
|
||||
point_type(edge.vertex0()->x(), edge.vertex0()->y()));
|
||||
}
|
||||
if (edge.vertex1() == NULL) {
|
||||
clipped_edge->push_back(point_type(
|
||||
origin.x() + direction.x() * koef,
|
||||
origin.y() + direction.y() * koef));
|
||||
} else {
|
||||
clipped_edge->push_back(
|
||||
point_type(edge.vertex1()->x(), edge.vertex1()->y()));
|
||||
}
|
||||
}
|
||||
|
||||
inline void sample_curved_edge(const std::vector<segment_type> &segments, const edge_type& edge, std::vector<point_type> &sampled_edge, coordinate_type max_dist)
|
||||
{
|
||||
point_type point = edge.cell()->contains_point() ?
|
||||
retrieve_point(segments, *edge.cell()) :
|
||||
retrieve_point(segments, *edge.twin()->cell());
|
||||
segment_type segment = edge.cell()->contains_point() ?
|
||||
segments[edge.twin()->cell()->source_index()] :
|
||||
segments[edge.cell()->source_index()];
|
||||
::boost::polygon::voronoi_visual_utils<coordinate_type>::discretize(point, segment, max_dist, &sampled_edge);
|
||||
}
|
||||
|
||||
} /* namespace Internal */ } // namespace Voronoi
|
||||
|
||||
void dump_voronoi_to_svg(const Lines &lines, /* const */ boost::polygon::voronoi_diagram<double> &vd, const ThickPolylines *polylines, const char *path)
|
||||
{
|
||||
const double scale = 0.2;
|
||||
const std::string inputSegmentPointColor = "lightseagreen";
|
||||
const coord_t inputSegmentPointRadius = coord_t(0.09 * scale / SCALING_FACTOR);
|
||||
const std::string inputSegmentColor = "lightseagreen";
|
||||
const coord_t inputSegmentLineWidth = coord_t(0.03 * scale / SCALING_FACTOR);
|
||||
|
||||
const std::string voronoiPointColor = "black";
|
||||
const coord_t voronoiPointRadius = coord_t(0.06 * scale / SCALING_FACTOR);
|
||||
const std::string voronoiLineColorPrimary = "black";
|
||||
const std::string voronoiLineColorSecondary = "green";
|
||||
const std::string voronoiArcColor = "red";
|
||||
const coord_t voronoiLineWidth = coord_t(0.02 * scale / SCALING_FACTOR);
|
||||
|
||||
const bool internalEdgesOnly = false;
|
||||
const bool primaryEdgesOnly = false;
|
||||
|
||||
BoundingBox bbox = BoundingBox(lines);
|
||||
bbox.min(0) -= coord_t(1. / SCALING_FACTOR);
|
||||
bbox.min(1) -= coord_t(1. / SCALING_FACTOR);
|
||||
bbox.max(0) += coord_t(1. / SCALING_FACTOR);
|
||||
bbox.max(1) += coord_t(1. / SCALING_FACTOR);
|
||||
|
||||
::Slic3r::SVG svg(path, bbox);
|
||||
|
||||
if (polylines != NULL)
|
||||
svg.draw(*polylines, "lime", "lime", voronoiLineWidth);
|
||||
|
||||
// bbox.scale(1.2);
|
||||
// For clipping of half-lines to some reasonable value.
|
||||
// The line will then be clipped by the SVG viewer anyway.
|
||||
const double bbox_dim_max = double(bbox.max(0) - bbox.min(0)) + double(bbox.max(1) - bbox.min(1));
|
||||
// For the discretization of the Voronoi parabolic segments.
|
||||
const double discretization_step = 0.0005 * bbox_dim_max;
|
||||
|
||||
// Make a copy of the input segments with the double type.
|
||||
std::vector<Voronoi::Internal::segment_type> segments;
|
||||
for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++ it)
|
||||
segments.push_back(Voronoi::Internal::segment_type(
|
||||
Voronoi::Internal::point_type(double(it->a(0)), double(it->a(1))),
|
||||
Voronoi::Internal::point_type(double(it->b(0)), double(it->b(1)))));
|
||||
|
||||
// Color exterior edges.
|
||||
for (boost::polygon::voronoi_diagram<double>::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it)
|
||||
if (!it->is_finite())
|
||||
Voronoi::Internal::color_exterior(&(*it));
|
||||
|
||||
// Draw the end points of the input polygon.
|
||||
for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) {
|
||||
svg.draw(it->a, inputSegmentPointColor, inputSegmentPointRadius);
|
||||
svg.draw(it->b, inputSegmentPointColor, inputSegmentPointRadius);
|
||||
}
|
||||
// Draw the input polygon.
|
||||
for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it)
|
||||
svg.draw(Line(Point(coord_t(it->a(0)), coord_t(it->a(1))), Point(coord_t(it->b(0)), coord_t(it->b(1)))), inputSegmentColor, inputSegmentLineWidth);
|
||||
|
||||
#if 1
|
||||
// Draw voronoi vertices.
|
||||
for (boost::polygon::voronoi_diagram<double>::const_vertex_iterator it = vd.vertices().begin(); it != vd.vertices().end(); ++it)
|
||||
if (! internalEdgesOnly || it->color() != Voronoi::Internal::EXTERNAL_COLOR)
|
||||
svg.draw(Point(coord_t(it->x()), coord_t(it->y())), voronoiPointColor, voronoiPointRadius);
|
||||
|
||||
for (boost::polygon::voronoi_diagram<double>::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it) {
|
||||
if (primaryEdgesOnly && !it->is_primary())
|
||||
continue;
|
||||
if (internalEdgesOnly && (it->color() == Voronoi::Internal::EXTERNAL_COLOR))
|
||||
continue;
|
||||
std::vector<Voronoi::Internal::point_type> samples;
|
||||
std::string color = voronoiLineColorPrimary;
|
||||
if (!it->is_finite()) {
|
||||
Voronoi::Internal::clip_infinite_edge(segments, *it, bbox_dim_max, &samples);
|
||||
if (! it->is_primary())
|
||||
color = voronoiLineColorSecondary;
|
||||
} else {
|
||||
// Store both points of the segment into samples. sample_curved_edge will split the initial line
|
||||
// until the discretization_step is reached.
|
||||
samples.push_back(Voronoi::Internal::point_type(it->vertex0()->x(), it->vertex0()->y()));
|
||||
samples.push_back(Voronoi::Internal::point_type(it->vertex1()->x(), it->vertex1()->y()));
|
||||
if (it->is_curved()) {
|
||||
Voronoi::Internal::sample_curved_edge(segments, *it, samples, discretization_step);
|
||||
color = voronoiArcColor;
|
||||
} else if (! it->is_primary())
|
||||
color = voronoiLineColorSecondary;
|
||||
}
|
||||
for (std::size_t i = 0; i + 1 < samples.size(); ++i)
|
||||
svg.draw(Line(Point(coord_t(samples[i].x()), coord_t(samples[i].y())), Point(coord_t(samples[i+1].x()), coord_t(samples[i+1].y()))), color, voronoiLineWidth);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (polylines != NULL)
|
||||
svg.draw(*polylines, "blue", voronoiLineWidth);
|
||||
|
||||
svg.Close();
|
||||
}
|
||||
#endif // SLIC3R_DEBUG
|
||||
|
||||
template<typename VD, typename SEGMENTS>
|
||||
inline const typename VD::point_type retrieve_cell_point(const typename VD::cell_type& cell, const SEGMENTS &segments)
|
||||
{
|
||||
assert(cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT || cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT);
|
||||
return (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? low(segments[cell.source_index()]) : high(segments[cell.source_index()]);
|
||||
}
|
||||
|
||||
template<typename VD, typename SEGMENTS>
|
||||
inline std::pair<typename VD::coord_type, typename VD::coord_type>
|
||||
measure_edge_thickness(const VD &vd, const typename VD::edge_type& edge, const SEGMENTS &segments)
|
||||
{
|
||||
typedef typename VD::coord_type T;
|
||||
const typename VD::point_type pa(edge.vertex0()->x(), edge.vertex0()->y());
|
||||
const typename VD::point_type pb(edge.vertex1()->x(), edge.vertex1()->y());
|
||||
const typename VD::cell_type &cell1 = *edge.cell();
|
||||
const typename VD::cell_type &cell2 = *edge.twin()->cell();
|
||||
if (cell1.contains_segment()) {
|
||||
if (cell2.contains_segment()) {
|
||||
// Both cells contain a linear segment, the left / right cells are symmetric.
|
||||
// Project pa, pb to the left segment.
|
||||
const typename VD::segment_type segment1 = segments[cell1.source_index()];
|
||||
const typename VD::point_type p1a = project_point_to_segment(segment1, pa);
|
||||
const typename VD::point_type p1b = project_point_to_segment(segment1, pb);
|
||||
return std::pair<T, T>(T(2.)*dist(pa, p1a), T(2.)*dist(pb, p1b));
|
||||
} else {
|
||||
// 1st cell contains a linear segment, 2nd cell contains a point.
|
||||
// The medial axis between the cells is a parabolic arc.
|
||||
// Project pa, pb to the left segment.
|
||||
const typename VD::point_type p2 = retrieve_cell_point<VD>(cell2, segments);
|
||||
return std::pair<T, T>(T(2.)*dist(pa, p2), T(2.)*dist(pb, p2));
|
||||
}
|
||||
} else if (cell2.contains_segment()) {
|
||||
// 1st cell contains a point, 2nd cell contains a linear segment.
|
||||
// The medial axis between the cells is a parabolic arc.
|
||||
const typename VD::point_type p1 = retrieve_cell_point<VD>(cell1, segments);
|
||||
return std::pair<T, T>(T(2.)*dist(pa, p1), T(2.)*dist(pb, p1));
|
||||
} else {
|
||||
// Both cells contain a point. The left / right regions are triangular and symmetric.
|
||||
const typename VD::point_type p1 = retrieve_cell_point<VD>(cell1, segments);
|
||||
return std::pair<T, T>(T(2.)*dist(pa, p1), T(2.)*dist(pb, p1));
|
||||
}
|
||||
}
|
||||
|
||||
// Converts the Line instances of Lines vector to VD::segment_type.
|
||||
template<typename VD>
|
||||
class Lines2VDSegments
|
||||
{
|
||||
public:
|
||||
Lines2VDSegments(const Lines &alines) : lines(alines) {}
|
||||
typename VD::segment_type operator[](size_t idx) const {
|
||||
return typename VD::segment_type(
|
||||
typename VD::point_type(typename VD::coord_type(lines[idx].a(0)), typename VD::coord_type(lines[idx].a(1))),
|
||||
typename VD::point_type(typename VD::coord_type(lines[idx].b(0)), typename VD::coord_type(lines[idx].b(1))));
|
||||
}
|
||||
private:
|
||||
const Lines &lines;
|
||||
};
|
||||
|
||||
void
|
||||
MedialAxis::build(ThickPolylines* polylines)
|
||||
{
|
||||
construct_voronoi(this->lines.begin(), this->lines.end(), &this->vd);
|
||||
|
||||
/*
|
||||
// DEBUG: dump all Voronoi edges
|
||||
{
|
||||
for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) {
|
||||
if (edge->is_infinite()) continue;
|
||||
|
||||
ThickPolyline polyline;
|
||||
polyline.points.push_back(Point( edge->vertex0()->x(), edge->vertex0()->y() ));
|
||||
polyline.points.push_back(Point( edge->vertex1()->x(), edge->vertex1()->y() ));
|
||||
polylines->push_back(polyline);
|
||||
}
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
//typedef const VD::vertex_type vert_t;
|
||||
typedef const VD::edge_type edge_t;
|
||||
|
||||
// collect valid edges (i.e. prune those not belonging to MAT)
|
||||
// note: this keeps twins, so it inserts twice the number of the valid edges
|
||||
this->valid_edges.clear();
|
||||
{
|
||||
std::set<const VD::edge_type*> seen_edges;
|
||||
for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) {
|
||||
// if we only process segments representing closed loops, none if the
|
||||
// infinite edges (if any) would be part of our MAT anyway
|
||||
if (edge->is_secondary() || edge->is_infinite()) continue;
|
||||
|
||||
// don't re-validate twins
|
||||
if (seen_edges.find(&*edge) != seen_edges.end()) continue; // TODO: is this needed?
|
||||
seen_edges.insert(&*edge);
|
||||
seen_edges.insert(edge->twin());
|
||||
|
||||
if (!this->validate_edge(&*edge)) continue;
|
||||
this->valid_edges.insert(&*edge);
|
||||
this->valid_edges.insert(edge->twin());
|
||||
}
|
||||
}
|
||||
this->edges = this->valid_edges;
|
||||
|
||||
// iterate through the valid edges to build polylines
|
||||
while (!this->edges.empty()) {
|
||||
const edge_t* edge = *this->edges.begin();
|
||||
|
||||
// start a polyline
|
||||
ThickPolyline polyline;
|
||||
polyline.points.push_back(Point( edge->vertex0()->x(), edge->vertex0()->y() ));
|
||||
polyline.points.push_back(Point( edge->vertex1()->x(), edge->vertex1()->y() ));
|
||||
polyline.width.push_back(this->thickness[edge].first);
|
||||
polyline.width.push_back(this->thickness[edge].second);
|
||||
|
||||
// remove this edge and its twin from the available edges
|
||||
(void)this->edges.erase(edge);
|
||||
(void)this->edges.erase(edge->twin());
|
||||
|
||||
// get next points
|
||||
this->process_edge_neighbors(edge, &polyline);
|
||||
|
||||
// get previous points
|
||||
{
|
||||
ThickPolyline rpolyline;
|
||||
this->process_edge_neighbors(edge->twin(), &rpolyline);
|
||||
polyline.points.insert(polyline.points.begin(), rpolyline.points.rbegin(), rpolyline.points.rend());
|
||||
polyline.width.insert(polyline.width.begin(), rpolyline.width.rbegin(), rpolyline.width.rend());
|
||||
polyline.endpoints.first = rpolyline.endpoints.second;
|
||||
}
|
||||
|
||||
assert(polyline.width.size() == polyline.points.size()*2 - 2);
|
||||
|
||||
// prevent loop endpoints from being extended
|
||||
if (polyline.first_point() == polyline.last_point()) {
|
||||
polyline.endpoints.first = false;
|
||||
polyline.endpoints.second = false;
|
||||
}
|
||||
|
||||
// append polyline to result
|
||||
polylines->push_back(polyline);
|
||||
}
|
||||
|
||||
#ifdef SLIC3R_DEBUG
|
||||
{
|
||||
static int iRun = 0;
|
||||
dump_voronoi_to_svg(this->lines, this->vd, polylines, debug_out_path("MedialAxis-%d.svg", iRun ++).c_str());
|
||||
printf("Thick lines: ");
|
||||
for (ThickPolylines::const_iterator it = polylines->begin(); it != polylines->end(); ++ it) {
|
||||
ThickLines lines = it->thicklines();
|
||||
for (ThickLines::const_iterator it2 = lines.begin(); it2 != lines.end(); ++ it2) {
|
||||
printf("%f,%f ", it2->a_width, it2->b_width);
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
#endif /* SLIC3R_DEBUG */
|
||||
}
|
||||
|
||||
void
|
||||
MedialAxis::build(Polylines* polylines)
|
||||
{
|
||||
ThickPolylines tp;
|
||||
this->build(&tp);
|
||||
polylines->insert(polylines->end(), tp.begin(), tp.end());
|
||||
}
|
||||
|
||||
void
|
||||
MedialAxis::process_edge_neighbors(const VD::edge_type* edge, ThickPolyline* polyline)
|
||||
{
|
||||
while (true) {
|
||||
// Since rot_next() works on the edge starting point but we want
|
||||
// to find neighbors on the ending point, we just swap edge with
|
||||
// its twin.
|
||||
const VD::edge_type* twin = edge->twin();
|
||||
|
||||
// count neighbors for this edge
|
||||
std::vector<const VD::edge_type*> neighbors;
|
||||
for (const VD::edge_type* neighbor = twin->rot_next(); neighbor != twin;
|
||||
neighbor = neighbor->rot_next()) {
|
||||
if (this->valid_edges.count(neighbor) > 0) neighbors.push_back(neighbor);
|
||||
}
|
||||
|
||||
// if we have a single neighbor then we can continue recursively
|
||||
if (neighbors.size() == 1) {
|
||||
const VD::edge_type* neighbor = neighbors.front();
|
||||
|
||||
// break if this is a closed loop
|
||||
if (this->edges.count(neighbor) == 0) return;
|
||||
|
||||
Point new_point(neighbor->vertex1()->x(), neighbor->vertex1()->y());
|
||||
polyline->points.push_back(new_point);
|
||||
polyline->width.push_back(this->thickness[neighbor].first);
|
||||
polyline->width.push_back(this->thickness[neighbor].second);
|
||||
(void)this->edges.erase(neighbor);
|
||||
(void)this->edges.erase(neighbor->twin());
|
||||
edge = neighbor;
|
||||
} else if (neighbors.size() == 0) {
|
||||
polyline->endpoints.second = true;
|
||||
return;
|
||||
} else {
|
||||
// T-shaped or star-shaped joint
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MedialAxis::validate_edge(const VD::edge_type* edge)
|
||||
{
|
||||
// prevent overflows and detect almost-infinite edges
|
||||
#ifndef CLIPPERLIB_INT32
|
||||
if (std::abs(edge->vertex0()->x()) > double(CLIPPER_MAX_COORD_UNSCALED) ||
|
||||
std::abs(edge->vertex0()->y()) > double(CLIPPER_MAX_COORD_UNSCALED) ||
|
||||
std::abs(edge->vertex1()->x()) > double(CLIPPER_MAX_COORD_UNSCALED) ||
|
||||
std::abs(edge->vertex1()->y()) > double(CLIPPER_MAX_COORD_UNSCALED))
|
||||
return false;
|
||||
#endif // CLIPPERLIB_INT32
|
||||
|
||||
// construct the line representing this edge of the Voronoi diagram
|
||||
const Line line(
|
||||
Point( edge->vertex0()->x(), edge->vertex0()->y() ),
|
||||
Point( edge->vertex1()->x(), edge->vertex1()->y() )
|
||||
);
|
||||
|
||||
// discard edge if it lies outside the supplied shape
|
||||
// this could maybe be optimized (checking inclusion of the endpoints
|
||||
// might give false positives as they might belong to the contour itself)
|
||||
if (this->expolygon != NULL) {
|
||||
if (line.a == line.b) {
|
||||
// in this case, contains(line) returns a false positive
|
||||
if (!this->expolygon->contains(line.a)) return false;
|
||||
} else {
|
||||
if (!this->expolygon->contains(line)) return false;
|
||||
}
|
||||
}
|
||||
|
||||
// retrieve the original line segments which generated the edge we're checking
|
||||
const VD::cell_type* cell_l = edge->cell();
|
||||
const VD::cell_type* cell_r = edge->twin()->cell();
|
||||
const Line &segment_l = this->retrieve_segment(cell_l);
|
||||
const Line &segment_r = this->retrieve_segment(cell_r);
|
||||
|
||||
/*
|
||||
SVG svg("edge.svg");
|
||||
svg.draw(*this->expolygon);
|
||||
svg.draw(line);
|
||||
svg.draw(segment_l, "red");
|
||||
svg.draw(segment_r, "blue");
|
||||
svg.Close();
|
||||
*/
|
||||
|
||||
/* Calculate thickness of the cross-section at both the endpoints of this edge.
|
||||
Our Voronoi edge is part of a CCW sequence going around its Voronoi cell
|
||||
located on the left side. (segment_l).
|
||||
This edge's twin goes around segment_r. Thus, segment_r is
|
||||
oriented in the same direction as our main edge, and segment_l is oriented
|
||||
in the same direction as our twin edge.
|
||||
We used to only consider the (half-)distances to segment_r, and that works
|
||||
whenever segment_l and segment_r are almost specular and facing. However,
|
||||
at curves they are staggered and they only face for a very little length
|
||||
(our very short edge represents such visibility).
|
||||
Both w0 and w1 can be calculated either towards cell_l or cell_r with equal
|
||||
results by Voronoi definition.
|
||||
When cell_l or cell_r don't refer to the segment but only to an endpoint, we
|
||||
calculate the distance to that endpoint instead. */
|
||||
|
||||
coordf_t w0 = cell_r->contains_segment()
|
||||
? segment_r.distance_to(line.a)*2
|
||||
: (this->retrieve_endpoint(cell_r) - line.a).cast<double>().norm()*2;
|
||||
|
||||
coordf_t w1 = cell_l->contains_segment()
|
||||
? segment_l.distance_to(line.b)*2
|
||||
: (this->retrieve_endpoint(cell_l) - line.b).cast<double>().norm()*2;
|
||||
|
||||
if (cell_l->contains_segment() && cell_r->contains_segment()) {
|
||||
// calculate the relative angle between the two boundary segments
|
||||
double angle = fabs(segment_r.orientation() - segment_l.orientation());
|
||||
if (angle > PI) angle = 2*PI - angle;
|
||||
assert(angle >= 0 && angle <= PI);
|
||||
|
||||
// fabs(angle) ranges from 0 (collinear, same direction) to PI (collinear, opposite direction)
|
||||
// we're interested only in segments close to the second case (facing segments)
|
||||
// so we allow some tolerance.
|
||||
// this filter ensures that we're dealing with a narrow/oriented area (longer than thick)
|
||||
// we don't run it on edges not generated by two segments (thus generated by one segment
|
||||
// and the endpoint of another segment), since their orientation would not be meaningful
|
||||
if (PI - angle > PI/8) {
|
||||
// angle is not narrow enough
|
||||
|
||||
// only apply this filter to segments that are not too short otherwise their
|
||||
// angle could possibly be not meaningful
|
||||
if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON || line.length() >= this->min_width)
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (w0 < this->min_width && w1 < this->min_width)
|
||||
return false;
|
||||
|
||||
if (w0 > this->max_width && w1 > this->max_width)
|
||||
return false;
|
||||
|
||||
this->thickness[edge] = std::make_pair(w0, w1);
|
||||
this->thickness[edge->twin()] = std::make_pair(w1, w0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const Line& MedialAxis::retrieve_segment(const VD::cell_type* cell) const
|
||||
{
|
||||
return this->lines[cell->source_index()];
|
||||
}
|
||||
|
||||
const Point& MedialAxis::retrieve_endpoint(const VD::cell_type* cell) const
|
||||
{
|
||||
const Line& line = this->retrieve_segment(cell);
|
||||
if (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) {
|
||||
return line.a;
|
||||
} else {
|
||||
return line.b;
|
||||
}
|
||||
}
|
||||
|
||||
} } // namespace Slicer::Geometry
|
33
src/libslic3r/Geometry/MedialAxis.hpp
Normal file
33
src/libslic3r/Geometry/MedialAxis.hpp
Normal file
|
@ -0,0 +1,33 @@
|
|||
#ifndef slic3r_Geometry_MedialAxis_hpp_
|
||||
#define slic3r_Geometry_MedialAxis_hpp_
|
||||
|
||||
#include "Voronoi.hpp"
|
||||
#include "../ExPolygon.hpp"
|
||||
|
||||
namespace Slic3r { namespace Geometry {
|
||||
|
||||
class MedialAxis {
|
||||
public:
|
||||
Lines lines;
|
||||
const ExPolygon* expolygon;
|
||||
double max_width;
|
||||
double min_width;
|
||||
MedialAxis(double _max_width, double _min_width, const ExPolygon* _expolygon = NULL)
|
||||
: expolygon(_expolygon), max_width(_max_width), min_width(_min_width) {};
|
||||
void build(ThickPolylines* polylines);
|
||||
void build(Polylines* polylines);
|
||||
|
||||
private:
|
||||
using VD = VoronoiDiagram;
|
||||
VD vd;
|
||||
std::set<const VD::edge_type*> edges, valid_edges;
|
||||
std::map<const VD::edge_type*, std::pair<coordf_t,coordf_t> > thickness;
|
||||
void process_edge_neighbors(const VD::edge_type* edge, ThickPolyline* polyline);
|
||||
bool validate_edge(const VD::edge_type* edge);
|
||||
const Line& retrieve_segment(const VD::cell_type* cell) const;
|
||||
const Point& retrieve_endpoint(const VD::cell_type* cell) const;
|
||||
};
|
||||
|
||||
} } // namespace Slicer::Geometry
|
||||
|
||||
#endif // slic3r_Geometry_MedialAxis_hpp_
|
33
src/libslic3r/Geometry/Voronoi.hpp
Normal file
33
src/libslic3r/Geometry/Voronoi.hpp
Normal file
|
@ -0,0 +1,33 @@
|
|||
#ifndef slic3r_Geometry_Voronoi_hpp_
|
||||
#define slic3r_Geometry_Voronoi_hpp_
|
||||
|
||||
#include "../Line.hpp"
|
||||
#include "../Polyline.hpp"
|
||||
|
||||
#define BOOST_VORONOI_USE_GMP 1
|
||||
|
||||
#ifdef _MSC_VER
|
||||
// Suppress warning C4146 in OpenVDB: unary minus operator applied to unsigned type, result still unsigned
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4146)
|
||||
#endif // _MSC_VER
|
||||
#include "boost/polygon/voronoi.hpp"
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif // _MSC_VER
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace Geometry {
|
||||
|
||||
class VoronoiDiagram : public boost::polygon::voronoi_diagram<double> {
|
||||
public:
|
||||
typedef double coord_type;
|
||||
typedef boost::polygon::point_data<coordinate_type> point_type;
|
||||
typedef boost::polygon::segment_data<coordinate_type> segment_type;
|
||||
typedef boost::polygon::rectangle_data<coordinate_type> rect_type;
|
||||
};
|
||||
|
||||
} } // namespace Slicer::Geometry
|
||||
|
||||
#endif // slic3r_Geometry_Voronoi_hpp_
|
|
@ -1,5 +1,6 @@
|
|||
// Polygon offsetting using Voronoi diagram prodiced by boost::polygon.
|
||||
|
||||
#include "Geometry.hpp"
|
||||
#include "VoronoiOffset.hpp"
|
||||
#include "libslic3r.h"
|
||||
|
|
@ -3,9 +3,9 @@
|
|||
#ifndef slic3r_VoronoiOffset_hpp_
|
||||
#define slic3r_VoronoiOffset_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "../libslic3r.h"
|
||||
|
||||
#include "Geometry.hpp"
|
||||
#include "Voronoi.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
|
@ -148,19 +148,23 @@ template<class _Mesh> TriangleMesh cgal_to_triangle_mesh(const _Mesh &cgalmesh)
|
|||
its.vertices.reserve(cgalmesh.num_vertices());
|
||||
its.indices.reserve(cgalmesh.num_faces());
|
||||
|
||||
for (auto &vi : cgalmesh.vertices()) {
|
||||
const auto &faces = cgalmesh.faces();
|
||||
const auto &vertices = cgalmesh.vertices();
|
||||
int vsize = int(vertices.size());
|
||||
|
||||
for (auto &vi : vertices) {
|
||||
auto &v = cgalmesh.point(vi); // Don't ask...
|
||||
its.vertices.emplace_back(to_vec3f(v));
|
||||
}
|
||||
|
||||
for (auto &face : cgalmesh.faces()) {
|
||||
|
||||
for (auto &face : faces) {
|
||||
auto vtc = cgalmesh.vertices_around_face(cgalmesh.halfedge(face));
|
||||
|
||||
int i = 0;
|
||||
Vec3i facet;
|
||||
for (auto v : vtc) {
|
||||
int iv = v;
|
||||
if (i > 2 || iv < 0 || iv >= int(cgalmesh.vertices().size())) { i = 0; break; }
|
||||
if (i > 2 || iv < 0 || iv >= vsize) { i = 0; break; }
|
||||
facet(i++) = iv;
|
||||
}
|
||||
|
||||
|
|
|
@ -1587,6 +1587,10 @@ unsigned int ModelObject::check_instances_print_volume_state(const Polygon& prin
|
|||
if (vol->is_model_part()) {
|
||||
const Transform3d matrix = model_instance->get_matrix() * vol->get_matrix();
|
||||
const BoundingBoxf3 bb = vol->mesh().transformed_bounding_box(matrix, 0.0);
|
||||
if (!bb.defined) {
|
||||
// this may happen if the part is fully below the printbed, leading to a crash in the following call to its_convex_hull_2d_above()
|
||||
continue;
|
||||
}
|
||||
const Polygon volume_hull_2d = its_convex_hull_2d_above(vol->mesh().its, matrix.cast<float>(), 0.0f);
|
||||
ModelInstanceEPrintVolumeState state = printbed_collision_state(printbed_shape, print_volume_height, volume_hull_2d, bb.min.z(), bb.max.z());
|
||||
if (state == ModelInstancePVS_Inside)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#include "EdgeGrid.hpp"
|
||||
#include "Layer.hpp"
|
||||
#include "Print.hpp"
|
||||
#include "VoronoiVisualUtils.hpp"
|
||||
#include "Geometry/VoronoiVisualUtils.hpp"
|
||||
#include "MutablePolygon.hpp"
|
||||
#include "format.hpp"
|
||||
|
||||
|
@ -44,6 +44,8 @@ struct segment_traits<Slic3r::ColoredLine> {
|
|||
//#define MMU_SEGMENTATION_DEBUG_GRAPH
|
||||
//#define MMU_SEGMENTATION_DEBUG_REGIONS
|
||||
//#define MMU_SEGMENTATION_DEBUG_INPUT
|
||||
//#define MMU_SEGMENTATION_DEBUG_PAINTED_LINES
|
||||
//#define MMU_SEGMENTATION_DEBUG_COLORIZED_POLYGONS
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
@ -147,18 +149,6 @@ struct PaintedLineVisitor
|
|||
static inline const double append_threshold2 = Slic3r::sqr(append_threshold);
|
||||
};
|
||||
|
||||
static std::vector<ColoredLine> to_colored_lines(const EdgeGrid::Contour &contour, int color)
|
||||
{
|
||||
std::vector<ColoredLine> lines;
|
||||
if (contour.num_segments() > 2) {
|
||||
lines.reserve(contour.num_segments());
|
||||
for (auto it = contour.begin(); it != contour.end() - 1; ++it)
|
||||
lines.push_back({Line(*it, *(it + 1)), color});
|
||||
lines.push_back({Line(contour.back(), contour.front()), color});
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
static Polygon colored_points_to_polygon(const std::vector<ColoredLine> &lines)
|
||||
{
|
||||
Polygon out;
|
||||
|
@ -242,25 +232,18 @@ static std::vector<std::vector<std::pair<size_t, size_t>>> get_all_segments(cons
|
|||
return all_segments;
|
||||
}
|
||||
|
||||
static std::vector<ColoredLine> colorize_line(const Line & line_to_process,
|
||||
const size_t start_idx,
|
||||
const size_t end_idx,
|
||||
std::vector<PaintedLine> &painted_lines)
|
||||
static std::vector<PaintedLine> filter_painted_lines(const Line &line_to_process, const size_t start_idx, const size_t end_idx, const std::vector<PaintedLine> &painted_lines)
|
||||
{
|
||||
std::vector<PaintedLine> internal_painted;
|
||||
for (size_t line_idx = start_idx; line_idx <= end_idx; ++line_idx)
|
||||
internal_painted.emplace_back(painted_lines[line_idx]);
|
||||
|
||||
const int filter_eps_value = scale_(0.1f);
|
||||
std::vector<PaintedLine> filtered_lines;
|
||||
filtered_lines.emplace_back(internal_painted.front());
|
||||
for (size_t line_idx = 1; line_idx < internal_painted.size(); ++line_idx) {
|
||||
filtered_lines.emplace_back(painted_lines[start_idx]);
|
||||
for (size_t line_idx = start_idx + 1; line_idx <= end_idx; ++line_idx) {
|
||||
// line_to_process is already all colored. Skip another possible duplicate coloring.
|
||||
if(filtered_lines.back().projected_line.b == line_to_process.b)
|
||||
break;
|
||||
|
||||
PaintedLine &prev = filtered_lines.back();
|
||||
PaintedLine &curr = internal_painted[line_idx];
|
||||
const PaintedLine &curr = painted_lines[line_idx];
|
||||
|
||||
double prev_length = prev.projected_line.length();
|
||||
double curr_dist_start = (curr.projected_line.a - prev.projected_line.a).cast<double>().norm();
|
||||
|
@ -278,31 +261,84 @@ static std::vector<ColoredLine> colorize_line(const Line & line_to_
|
|||
}
|
||||
} else {
|
||||
double curr_dist_end = (curr.projected_line.b - prev.projected_line.a).cast<double>().norm();
|
||||
if (curr_dist_end <= prev_length) {
|
||||
} else {
|
||||
if (prev.color == curr.color) {
|
||||
if (curr_dist_end > prev_length) {
|
||||
if (prev.color == curr.color)
|
||||
prev.projected_line.b = curr.projected_line.b;
|
||||
} else {
|
||||
curr.projected_line.a = prev.projected_line.b;
|
||||
filtered_lines.emplace_back(curr);
|
||||
}
|
||||
else
|
||||
filtered_lines.push_back({curr.contour_idx, curr.line_idx, Line{prev.projected_line.b, curr.projected_line.b}, curr.color});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ColoredLine> final_lines;
|
||||
double dist_to_start = (filtered_lines.front().projected_line.a - line_to_process.a).cast<double>().norm();
|
||||
if (dist_to_start <= filter_eps_value) {
|
||||
if (double dist_to_start = (filtered_lines.front().projected_line.a - line_to_process.a).cast<double>().norm(); dist_to_start <= filter_eps_value)
|
||||
filtered_lines.front().projected_line.a = line_to_process.a;
|
||||
final_lines.push_back({filtered_lines.front().projected_line, filtered_lines.front().color});
|
||||
} else {
|
||||
final_lines.push_back({Line(line_to_process.a, filtered_lines.front().projected_line.a), 0});
|
||||
final_lines.push_back({filtered_lines.front().projected_line, filtered_lines.front().color});
|
||||
|
||||
if (double dist_to_end = (filtered_lines.back().projected_line.b - line_to_process.b).cast<double>().norm(); dist_to_end <= filter_eps_value)
|
||||
filtered_lines.back().projected_line.b = line_to_process.b;
|
||||
|
||||
return filtered_lines;
|
||||
}
|
||||
|
||||
static std::vector<std::vector<PaintedLine>> post_process_painted_lines(const std::vector<EdgeGrid::Contour> &contours, std::vector<PaintedLine> &&painted_lines)
|
||||
{
|
||||
if (painted_lines.empty())
|
||||
return {};
|
||||
|
||||
auto comp = [&contours](const PaintedLine &first, const PaintedLine &second) {
|
||||
Point first_start_p = contours[first.contour_idx].segment_start(first.line_idx);
|
||||
return first.contour_idx < second.contour_idx ||
|
||||
(first.contour_idx == second.contour_idx &&
|
||||
(first.line_idx < second.line_idx ||
|
||||
(first.line_idx == second.line_idx &&
|
||||
((first.projected_line.a - first_start_p).cast<double>().squaredNorm() < (second.projected_line.a - first_start_p).cast<double>().squaredNorm() ||
|
||||
((first.projected_line.a - first_start_p).cast<double>().squaredNorm() == (second.projected_line.a - first_start_p).cast<double>().squaredNorm() &&
|
||||
(first.projected_line.b - first.projected_line.a).cast<double>().squaredNorm() < (second.projected_line.b - second.projected_line.a).cast<double>().squaredNorm())))));
|
||||
};
|
||||
std::sort(painted_lines.begin(), painted_lines.end(), comp);
|
||||
|
||||
std::vector<std::vector<PaintedLine>> filtered_painted_lines(contours.size());
|
||||
size_t prev_painted_line_idx = 0;
|
||||
for (size_t curr_painted_line_idx = 0; curr_painted_line_idx < painted_lines.size(); ++curr_painted_line_idx) {
|
||||
size_t next_painted_line_idx = curr_painted_line_idx + 1;
|
||||
if (next_painted_line_idx >= painted_lines.size() || painted_lines[curr_painted_line_idx].contour_idx != painted_lines[next_painted_line_idx].contour_idx || painted_lines[curr_painted_line_idx].line_idx != painted_lines[next_painted_line_idx].line_idx) {
|
||||
const PaintedLine &start_line = painted_lines[prev_painted_line_idx];
|
||||
const Line &line_to_process = contours[start_line.contour_idx].get_segment(start_line.line_idx);
|
||||
Slic3r::append(filtered_painted_lines[painted_lines[curr_painted_line_idx].contour_idx], filter_painted_lines(line_to_process, prev_painted_line_idx, curr_painted_line_idx, painted_lines));
|
||||
prev_painted_line_idx = next_painted_line_idx;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t line_idx = 1; line_idx < filtered_lines.size(); ++line_idx) {
|
||||
ColoredLine &prev = final_lines.back();
|
||||
PaintedLine &curr = filtered_lines[line_idx];
|
||||
return filtered_painted_lines;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
static bool are_lines_connected(const std::vector<ColoredLine> &colored_lines)
|
||||
{
|
||||
for (size_t line_idx = 1; line_idx < colored_lines.size(); ++line_idx)
|
||||
if (colored_lines[line_idx - 1].line.b != colored_lines[line_idx].line.a)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
static std::vector<ColoredLine> colorize_line(const Line &line_to_process,
|
||||
const size_t start_idx,
|
||||
const size_t end_idx,
|
||||
const std::vector<PaintedLine> &painted_contour)
|
||||
{
|
||||
assert(start_idx < painted_contour.size() && end_idx < painted_contour.size() && start_idx <= end_idx);
|
||||
assert(std::all_of(painted_contour.begin() + start_idx, painted_contour.begin() + end_idx + 1, [&painted_contour, &start_idx](const auto &p_line) { return painted_contour[start_idx].line_idx == p_line.line_idx; }));
|
||||
|
||||
const int filter_eps_value = scale_(0.1f);
|
||||
std::vector<ColoredLine> final_lines;
|
||||
const PaintedLine &first_line = painted_contour[start_idx];
|
||||
if (double dist_to_start = (first_line.projected_line.a - line_to_process.a).cast<double>().norm(); dist_to_start > filter_eps_value)
|
||||
final_lines.push_back({Line(line_to_process.a, first_line.projected_line.a), 0});
|
||||
final_lines.push_back({first_line.projected_line, first_line.color});
|
||||
|
||||
for (size_t line_idx = start_idx + 1; line_idx <= end_idx; ++line_idx) {
|
||||
ColoredLine &prev = final_lines.back();
|
||||
const PaintedLine &curr = painted_contour[line_idx];
|
||||
|
||||
double line_dist = (curr.projected_line.a - prev.line.b).cast<double>().norm();
|
||||
if (line_dist <= filter_eps_value) {
|
||||
|
@ -318,18 +354,16 @@ static std::vector<ColoredLine> colorize_line(const Line & line_to_
|
|||
}
|
||||
}
|
||||
|
||||
double dist_to_end = (final_lines.back().line.b - line_to_process.b).cast<double>().norm();
|
||||
if (dist_to_end <= filter_eps_value)
|
||||
final_lines.back().line.b = line_to_process.b;
|
||||
else
|
||||
// If there is non-painted space, then inserts line painted by a default color.
|
||||
if (double dist_to_end = (final_lines.back().line.b - line_to_process.b).cast<double>().norm(); dist_to_end > filter_eps_value)
|
||||
final_lines.push_back({Line(final_lines.back().line.b, line_to_process.b), 0});
|
||||
|
||||
for (size_t line_idx = 1; line_idx < final_lines.size(); ++line_idx)
|
||||
assert(final_lines[line_idx - 1].line.b == final_lines[line_idx].line.a);
|
||||
// Make sure all the lines are connected.
|
||||
assert(are_lines_connected(final_lines));
|
||||
|
||||
for (size_t line_idx = 2; line_idx < final_lines.size(); ++line_idx) {
|
||||
const ColoredLine &line_0 = final_lines[line_idx - 2];
|
||||
ColoredLine & line_1 = final_lines[line_idx - 1];
|
||||
ColoredLine &line_1 = final_lines[line_idx - 1];
|
||||
const ColoredLine &line_2 = final_lines[line_idx - 0];
|
||||
|
||||
if (line_0.color == line_2.color && line_0.color != line_1.color)
|
||||
|
@ -349,52 +383,25 @@ static std::vector<ColoredLine> colorize_line(const Line & line_to_
|
|||
|
||||
final_lines = colored_lines_simple;
|
||||
|
||||
if (final_lines.size() > 1) {
|
||||
if (final_lines.size() > 1)
|
||||
if (final_lines.front().color != final_lines[1].color && final_lines.front().line.length() <= scale_(0.2)) {
|
||||
final_lines[1].line.a = final_lines.front().line.a;
|
||||
final_lines.erase(final_lines.begin());
|
||||
}
|
||||
}
|
||||
|
||||
if (final_lines.size() > 1) {
|
||||
if (final_lines.size() > 1)
|
||||
if (final_lines.back().color != final_lines[final_lines.size() - 2].color && final_lines.back().line.length() <= scale_(0.2)) {
|
||||
final_lines[final_lines.size() - 2].line.b = final_lines.back().line.b;
|
||||
final_lines.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
return final_lines;
|
||||
}
|
||||
|
||||
static std::vector<ColoredLine> colorize_polygon(const EdgeGrid::Contour &contour, const size_t start_idx, const size_t end_idx, std::vector<PaintedLine> &painted_lines)
|
||||
{
|
||||
std::vector<ColoredLine> new_lines;
|
||||
new_lines.reserve(end_idx - start_idx + 1);
|
||||
for (size_t idx = 0; idx < painted_lines[start_idx].line_idx; ++idx)
|
||||
new_lines.emplace_back(ColoredLine{contour.get_segment(idx), 0});
|
||||
|
||||
for (size_t first_idx = start_idx; first_idx <= end_idx; ++first_idx) {
|
||||
size_t second_idx = first_idx;
|
||||
while (second_idx <= end_idx && painted_lines[first_idx].line_idx == painted_lines[second_idx].line_idx) ++second_idx;
|
||||
--second_idx;
|
||||
|
||||
assert(painted_lines[first_idx].line_idx == painted_lines[second_idx].line_idx);
|
||||
std::vector<ColoredLine> lines_c_line = colorize_line(contour.get_segment(painted_lines[first_idx].line_idx), first_idx, second_idx, painted_lines);
|
||||
new_lines.insert(new_lines.end(), lines_c_line.begin(), lines_c_line.end());
|
||||
|
||||
if (second_idx + 1 <= end_idx)
|
||||
for (size_t idx = painted_lines[second_idx].line_idx + 1; idx < painted_lines[second_idx + 1].line_idx; ++idx)
|
||||
new_lines.emplace_back(ColoredLine{contour.get_segment(idx), 0});
|
||||
|
||||
first_idx = second_idx;
|
||||
}
|
||||
|
||||
for (size_t idx = painted_lines[end_idx].line_idx + 1; idx < contour.num_segments(); ++idx)
|
||||
new_lines.emplace_back(ColoredLine{contour.get_segment(idx), 0});
|
||||
|
||||
static std::vector<ColoredLine> filter_colorized_polygon(std::vector<ColoredLine> &&new_lines) {
|
||||
for (size_t line_idx = 2; line_idx < new_lines.size(); ++line_idx) {
|
||||
const ColoredLine &line_0 = new_lines[line_idx - 2];
|
||||
ColoredLine & line_1 = new_lines[line_idx - 1];
|
||||
ColoredLine &line_1 = new_lines[line_idx - 1];
|
||||
const ColoredLine &line_2 = new_lines[line_idx - 0];
|
||||
|
||||
if (line_0.color == line_2.color && line_0.color != line_1.color && line_0.color >= 1) {
|
||||
|
@ -404,8 +411,8 @@ static std::vector<ColoredLine> colorize_polygon(const EdgeGrid::Contour &contou
|
|||
|
||||
for (size_t line_idx = 3; line_idx < new_lines.size(); ++line_idx) {
|
||||
const ColoredLine &line_0 = new_lines[line_idx - 3];
|
||||
ColoredLine & line_1 = new_lines[line_idx - 2];
|
||||
ColoredLine & line_2 = new_lines[line_idx - 1];
|
||||
ColoredLine &line_1 = new_lines[line_idx - 2];
|
||||
ColoredLine &line_2 = new_lines[line_idx - 1];
|
||||
const ColoredLine &line_3 = new_lines[line_idx - 0];
|
||||
|
||||
if (line_0.color == line_3.color && (line_0.color != line_1.color || line_0.color != line_2.color) && line_0.color >= 1 && line_3.color >= 1) {
|
||||
|
@ -425,79 +432,110 @@ static std::vector<ColoredLine> colorize_polygon(const EdgeGrid::Contour &contou
|
|||
return total_length;
|
||||
};
|
||||
|
||||
for (size_t pair_idx = 1; pair_idx < segments.size(); ++pair_idx) {
|
||||
int color0 = new_lines[segments[pair_idx - 1].first].color;
|
||||
int color1 = new_lines[segments[pair_idx - 0].first].color;
|
||||
if (segments.size() >= 2)
|
||||
for (size_t curr_idx = 0; curr_idx < segments.size(); ++curr_idx) {
|
||||
size_t next_idx = next_idx_modulo(curr_idx, segments.size());
|
||||
assert(curr_idx != next_idx);
|
||||
|
||||
double seg0l = segment_length(segments[pair_idx - 1]);
|
||||
double seg1l = segment_length(segments[pair_idx - 0]);
|
||||
int color0 = new_lines[segments[curr_idx].first].color;
|
||||
int color1 = new_lines[segments[next_idx].first].color;
|
||||
|
||||
if (color0 != color1 && seg0l >= scale_(0.1) && seg1l <= scale_(0.2)) {
|
||||
for (size_t seg_start_idx = segments[pair_idx].first; seg_start_idx != segments[pair_idx].second; seg_start_idx = (seg_start_idx + 1 < new_lines.size()) ? seg_start_idx + 1 : 0)
|
||||
new_lines[seg_start_idx].color = color0;
|
||||
new_lines[segments[pair_idx].second].color = color0;
|
||||
double seg0l = segment_length(segments[curr_idx]);
|
||||
double seg1l = segment_length(segments[next_idx]);
|
||||
|
||||
if (color0 != color1 && seg0l >= scale_(0.1) && seg1l <= scale_(0.2)) {
|
||||
for (size_t seg_start_idx = segments[next_idx].first; seg_start_idx != segments[next_idx].second; seg_start_idx = (seg_start_idx + 1 < new_lines.size()) ? seg_start_idx + 1 : 0)
|
||||
new_lines[seg_start_idx].color = color0;
|
||||
new_lines[segments[next_idx].second].color = color0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
segments = get_segments(new_lines);
|
||||
for (size_t pair_idx = 1; pair_idx < segments.size(); ++pair_idx) {
|
||||
int color0 = new_lines[segments[pair_idx - 1].first].color;
|
||||
int color1 = new_lines[segments[pair_idx - 0].first].color;
|
||||
double seg1l = segment_length(segments[pair_idx - 0]);
|
||||
if (segments.size() >= 2)
|
||||
for (size_t curr_idx = 0; curr_idx < segments.size(); ++curr_idx) {
|
||||
size_t next_idx = next_idx_modulo(curr_idx, segments.size());
|
||||
assert(curr_idx != next_idx);
|
||||
|
||||
if (color0 >= 1 && color0 != color1 && seg1l <= scale_(0.2)) {
|
||||
for (size_t seg_start_idx = segments[pair_idx].first; seg_start_idx != segments[pair_idx].second; seg_start_idx = (seg_start_idx + 1 < new_lines.size()) ? seg_start_idx + 1 : 0)
|
||||
new_lines[seg_start_idx].color = color0;
|
||||
new_lines[segments[pair_idx].second].color = color0;
|
||||
int color0 = new_lines[segments[curr_idx].first].color;
|
||||
int color1 = new_lines[segments[next_idx].first].color;
|
||||
double seg1l = segment_length(segments[next_idx]);
|
||||
|
||||
if (color0 >= 1 && color0 != color1 && seg1l <= scale_(0.2)) {
|
||||
for (size_t seg_start_idx = segments[next_idx].first; seg_start_idx != segments[next_idx].second; seg_start_idx = (seg_start_idx + 1 < new_lines.size()) ? seg_start_idx + 1 : 0)
|
||||
new_lines[seg_start_idx].color = color0;
|
||||
new_lines[segments[next_idx].second].color = color0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t pair_idx = 2; pair_idx < segments.size(); ++pair_idx) {
|
||||
int color0 = new_lines[segments[pair_idx - 2].first].color;
|
||||
int color1 = new_lines[segments[pair_idx - 1].first].color;
|
||||
int color2 = new_lines[segments[pair_idx - 0].first].color;
|
||||
segments = get_segments(new_lines);
|
||||
if (segments.size() >= 3)
|
||||
for (size_t curr_idx = 0; curr_idx < segments.size(); ++curr_idx) {
|
||||
size_t next_idx = next_idx_modulo(curr_idx, segments.size());
|
||||
size_t next_next_idx = next_idx_modulo(next_idx, segments.size());
|
||||
|
||||
if (color0 > 0 && color0 == color2 && color0 != color1 && segment_length(segments[pair_idx - 1]) <= scale_(0.5)) {
|
||||
for (size_t seg_start_idx = segments[pair_idx].first; seg_start_idx != segments[pair_idx].second; seg_start_idx = (seg_start_idx + 1 < new_lines.size()) ? seg_start_idx + 1 : 0)
|
||||
new_lines[seg_start_idx].color = color0;
|
||||
new_lines[segments[pair_idx].second].color = color0;
|
||||
int color0 = new_lines[segments[curr_idx].first].color;
|
||||
int color1 = new_lines[segments[next_idx].first].color;
|
||||
int color2 = new_lines[segments[next_next_idx].first].color;
|
||||
|
||||
if (color0 > 0 && color0 == color2 && color0 != color1 && segment_length(segments[next_idx]) <= scale_(0.5)) {
|
||||
for (size_t seg_start_idx = segments[next_next_idx].first; seg_start_idx != segments[next_next_idx].second; seg_start_idx = (seg_start_idx + 1 < new_lines.size()) ? seg_start_idx + 1 : 0)
|
||||
new_lines[seg_start_idx].color = color0;
|
||||
new_lines[segments[next_next_idx].second].color = color0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new_lines;
|
||||
return std::move(new_lines);
|
||||
}
|
||||
|
||||
static std::vector<std::vector<ColoredLine>> colorize_polygons(const std::vector<EdgeGrid::Contour> &contours, std::vector<PaintedLine> &painted_lines)
|
||||
{
|
||||
const size_t start_idx = 0;
|
||||
const size_t end_idx = painted_lines.size() - 1;
|
||||
static std::vector<ColoredLine> colorize_contour(const EdgeGrid::Contour &contour, const std::vector<PaintedLine> &painted_contour) {
|
||||
assert(painted_contour.empty() || std::all_of(painted_contour.begin(), painted_contour.end(), [&painted_contour](const auto &p_line) { return painted_contour.front().contour_idx == p_line.contour_idx; }));
|
||||
|
||||
std::vector<std::vector<ColoredLine>> new_polygons;
|
||||
new_polygons.reserve(contours.size());
|
||||
|
||||
for (size_t idx = 0; idx < painted_lines[start_idx].contour_idx; ++idx)
|
||||
new_polygons.emplace_back(to_colored_lines(contours[idx], 0));
|
||||
|
||||
for (size_t first_idx = start_idx; first_idx <= end_idx; ++first_idx) {
|
||||
size_t second_idx = first_idx;
|
||||
while (second_idx <= end_idx && painted_lines[first_idx].contour_idx == painted_lines[second_idx].contour_idx)
|
||||
++second_idx;
|
||||
--second_idx;
|
||||
|
||||
assert(painted_lines[first_idx].contour_idx == painted_lines[second_idx].contour_idx);
|
||||
new_polygons.emplace_back(colorize_polygon(contours[painted_lines[first_idx].contour_idx], first_idx, second_idx, painted_lines));
|
||||
|
||||
if (second_idx + 1 <= end_idx)
|
||||
for (size_t idx = painted_lines[second_idx].contour_idx + 1; idx < painted_lines[second_idx + 1].contour_idx; ++idx)
|
||||
new_polygons.emplace_back(to_colored_lines(contours[idx], 0));
|
||||
|
||||
first_idx = second_idx;
|
||||
std::vector<ColoredLine> colorized_contour;
|
||||
if (painted_contour.empty()) {
|
||||
// Appends contour with default color for lines before the first PaintedLine.
|
||||
colorized_contour.reserve(contour.num_segments());
|
||||
for (const Line &line : contour.get_segments())
|
||||
colorized_contour.emplace_back(ColoredLine{line, 0});
|
||||
return colorized_contour;
|
||||
}
|
||||
|
||||
for (size_t idx = painted_lines[end_idx].contour_idx + 1; idx < contours.size(); ++idx)
|
||||
new_polygons.emplace_back(to_colored_lines(contours[idx], 0));
|
||||
colorized_contour.reserve(contour.num_segments() + painted_contour.size());
|
||||
for (size_t idx = 0; idx < painted_contour.front().line_idx; ++idx)
|
||||
colorized_contour.emplace_back(ColoredLine{contour.get_segment(idx), 0});
|
||||
|
||||
return new_polygons;
|
||||
size_t prev_painted_line_idx = 0;
|
||||
for (size_t curr_painted_line_idx = 0; curr_painted_line_idx < painted_contour.size(); ++curr_painted_line_idx) {
|
||||
size_t next_painted_line_idx = curr_painted_line_idx + 1;
|
||||
if (next_painted_line_idx >= painted_contour.size() || painted_contour[curr_painted_line_idx].line_idx != painted_contour[next_painted_line_idx].line_idx) {
|
||||
const std::vector<PaintedLine> &painted_contour_copy = painted_contour;
|
||||
Slic3r::append(colorized_contour, colorize_line(contour.get_segment(painted_contour[prev_painted_line_idx].line_idx), prev_painted_line_idx, curr_painted_line_idx, painted_contour_copy));
|
||||
|
||||
// Appends contour with default color for lines between the current and the next PaintedLine.
|
||||
if (next_painted_line_idx < painted_contour.size())
|
||||
for (size_t idx = painted_contour[curr_painted_line_idx].line_idx + 1; idx < painted_contour[next_painted_line_idx].line_idx; ++idx)
|
||||
colorized_contour.emplace_back(ColoredLine{contour.get_segment(idx), 0});
|
||||
|
||||
prev_painted_line_idx = next_painted_line_idx;
|
||||
}
|
||||
}
|
||||
|
||||
// Appends contour with default color for lines after the last PaintedLine.
|
||||
for (size_t idx = painted_contour.back().line_idx + 1; idx < contour.num_segments(); ++idx)
|
||||
colorized_contour.emplace_back(ColoredLine{contour.get_segment(idx), 0});
|
||||
|
||||
assert(!colorized_contour.empty());
|
||||
return filter_colorized_polygon(std::move(colorized_contour));
|
||||
}
|
||||
|
||||
static std::vector<std::vector<ColoredLine>> colorize_contours(const std::vector<EdgeGrid::Contour> &contours, const std::vector<std::vector<PaintedLine>> &painted_contours)
|
||||
{
|
||||
assert(contours.size() == painted_contours.size());
|
||||
std::vector<std::vector<ColoredLine>> colorized_contours(contours.size());
|
||||
for (const std::vector<PaintedLine> &painted_contour : painted_contours) {
|
||||
size_t contour_idx = &painted_contour - &painted_contours.front();
|
||||
colorized_contours[contour_idx] = colorize_contour(contours[contour_idx], painted_contours[contour_idx]);
|
||||
}
|
||||
return colorized_contours;
|
||||
}
|
||||
|
||||
using boost::polygon::voronoi_diagram;
|
||||
|
@ -1582,6 +1620,39 @@ void export_processed_input_expolygons_to_svg(const std::string &path, const Lay
|
|||
}
|
||||
#endif // MMU_SEGMENTATION_DEBUG_INPUT
|
||||
|
||||
#ifdef MMU_SEGMENTATION_DEBUG_PAINTED_LINES
|
||||
static void export_painted_lines_to_svg(const std::string &path, const std::vector<std::vector<PaintedLine>> &all_painted_lines, const ExPolygons &lslices)
|
||||
{
|
||||
const std::vector<std::string> colors = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "yellow"};
|
||||
coordf_t stroke_width = scale_(0.05);
|
||||
BoundingBox bbox = get_extents(lslices);
|
||||
bbox.offset(scale_(1.));
|
||||
::Slic3r::SVG svg(path.c_str(), bbox);
|
||||
|
||||
for (const Line &line : to_lines(lslices))
|
||||
svg.draw(line, "green", stroke_width);
|
||||
|
||||
for (const std::vector<PaintedLine> &painted_lines : all_painted_lines)
|
||||
for (const PaintedLine &painted_line : painted_lines)
|
||||
svg.draw(painted_line.projected_line, painted_line.color < int(colors.size()) ? colors[painted_line.color] : "black", stroke_width);
|
||||
}
|
||||
#endif // MMU_SEGMENTATION_DEBUG_PAINTED_LINES
|
||||
|
||||
#ifdef MMU_SEGMENTATION_DEBUG_COLORIZED_POLYGONS
|
||||
static void export_colorized_polygons_to_svg(const std::string &path, const std::vector<std::vector<ColoredLine>> &colorized_polygons, const ExPolygons &lslices)
|
||||
{
|
||||
const std::vector<std::string> colors = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "green", "yellow"};
|
||||
coordf_t stroke_width = scale_(0.05);
|
||||
BoundingBox bbox = get_extents(lslices);
|
||||
bbox.offset(scale_(1.));
|
||||
::Slic3r::SVG svg(path.c_str(), bbox);
|
||||
|
||||
for (const std::vector<ColoredLine> &colorized_polygon : colorized_polygons)
|
||||
for (const ColoredLine &colorized_line : colorized_polygon)
|
||||
svg.draw(colorized_line.line, colorized_line.color < int(colors.size())? colors[colorized_line.color] : "black", stroke_width);
|
||||
}
|
||||
#endif // MMU_SEGMENTATION_DEBUG_COLORIZED_POLYGONS
|
||||
|
||||
// Check if all ColoredLine representing a single layer uses the same color.
|
||||
static bool has_layer_only_one_color(const std::vector<std::vector<ColoredLine>> &colored_polygons)
|
||||
{
|
||||
|
@ -1731,22 +1802,32 @@ std::vector<std::vector<std::pair<ExPolygon, size_t>>> multi_material_segmentati
|
|||
tbb::parallel_for(tbb::blocked_range<size_t>(0, print_object.layers().size()), [&edge_grids, &input_expolygons, &painted_lines, &segmented_regions, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
|
||||
throw_on_cancel_callback();
|
||||
auto comp = [&edge_grids, layer_idx](const PaintedLine &first, const PaintedLine &second) {
|
||||
Point first_start_p = edge_grids[layer_idx].contours()[first.contour_idx].segment_start(first.line_idx);
|
||||
return first.contour_idx < second.contour_idx ||
|
||||
(first.contour_idx == second.contour_idx &&
|
||||
(first.line_idx < second.line_idx ||
|
||||
(first.line_idx == second.line_idx &&
|
||||
((first.projected_line.a - first_start_p).cast<double>().squaredNorm() < (second.projected_line.a - first_start_p).cast<double>().squaredNorm() ||
|
||||
((first.projected_line.a - first_start_p).cast<double>().squaredNorm() == (second.projected_line.a - first_start_p).cast<double>().squaredNorm() &&
|
||||
(first.projected_line.b - first.projected_line.a).cast<double>().squaredNorm() < (second.projected_line.b - second.projected_line.a).cast<double>().squaredNorm())))));
|
||||
};
|
||||
if (!painted_lines[layer_idx].empty()) {
|
||||
#ifdef MMU_SEGMENTATION_DEBUG_PAINTED_LINES
|
||||
{
|
||||
static int iRun = 0;
|
||||
export_painted_lines_to_svg(debug_out_path("mm-painted-lines-%d-%d.svg", layer_idx, iRun++), {painted_lines[layer_idx]}, input_expolygons[layer_idx]);
|
||||
}
|
||||
#endif // MMU_SEGMENTATION_DEBUG_PAINTED_LINES
|
||||
|
||||
std::sort(painted_lines[layer_idx].begin(), painted_lines[layer_idx].end(), comp);
|
||||
std::vector<PaintedLine> &painted_lines_single = painted_lines[layer_idx];
|
||||
std::vector<std::vector<PaintedLine>> post_processed_painted_lines = post_process_painted_lines(edge_grids[layer_idx].contours(), std::move(painted_lines[layer_idx]));
|
||||
|
||||
#ifdef MMU_SEGMENTATION_DEBUG_PAINTED_LINES
|
||||
{
|
||||
static int iRun = 0;
|
||||
export_painted_lines_to_svg(debug_out_path("mm-painted-lines-post-processed-%d-%d.svg", layer_idx, iRun++), post_processed_painted_lines, input_expolygons[layer_idx]);
|
||||
}
|
||||
#endif // MMU_SEGMENTATION_DEBUG_PAINTED_LINES
|
||||
|
||||
std::vector<std::vector<ColoredLine>> color_poly = colorize_contours(edge_grids[layer_idx].contours(), post_processed_painted_lines);
|
||||
|
||||
#ifdef MMU_SEGMENTATION_DEBUG_COLORIZED_POLYGONS
|
||||
{
|
||||
static int iRun = 0;
|
||||
export_colorized_polygons_to_svg(debug_out_path("mm-colorized_polygons-%d-%d.svg", layer_idx, iRun++), color_poly, input_expolygons[layer_idx]);
|
||||
}
|
||||
#endif // MMU_SEGMENTATION_DEBUG_COLORIZED_POLYGONS
|
||||
|
||||
if (!painted_lines_single.empty()) {
|
||||
std::vector<std::vector<ColoredLine>> color_poly = colorize_polygons(edge_grids[layer_idx].contours(), painted_lines_single);
|
||||
assert(!color_poly.empty());
|
||||
assert(!color_poly.front().empty());
|
||||
if (has_layer_only_one_color(color_poly)) {
|
||||
|
|
|
@ -238,6 +238,7 @@ namespace client
|
|||
int i() const { return data.i; }
|
||||
void set_i(int v) { this->reset(); this->data.i = v; this->type = TYPE_INT; }
|
||||
int as_i() const { return (this->type == TYPE_INT) ? this->i() : int(this->d()); }
|
||||
int as_i_rounded() const { return (this->type == TYPE_INT) ? this->i() : int(std::round(this->d())); }
|
||||
double& d() { return data.d; }
|
||||
double d() const { return data.d; }
|
||||
void set_d(double v) { this->reset(); this->data.d = v; this->type = TYPE_DOUBLE; }
|
||||
|
@ -319,7 +320,7 @@ namespace client
|
|||
expr unary_integer(const Iterator start_pos) const
|
||||
{
|
||||
switch (this->type) {
|
||||
case TYPE_INT :
|
||||
case TYPE_INT:
|
||||
return expr<Iterator>(this->i(), start_pos, this->it_range.end());
|
||||
case TYPE_DOUBLE:
|
||||
return expr<Iterator>(static_cast<int>(this->d()), start_pos, this->it_range.end());
|
||||
|
@ -331,10 +332,25 @@ namespace client
|
|||
return expr();
|
||||
}
|
||||
|
||||
expr round(const Iterator start_pos) const
|
||||
{
|
||||
switch (this->type) {
|
||||
case TYPE_INT:
|
||||
return expr<Iterator>(this->i(), start_pos, this->it_range.end());
|
||||
case TYPE_DOUBLE:
|
||||
return expr<Iterator>(static_cast<int>(std::round(this->d())), start_pos, this->it_range.end());
|
||||
default:
|
||||
this->throw_exception("Cannot round a non-numeric value.");
|
||||
}
|
||||
assert(false);
|
||||
// Suppress compiler warnings.
|
||||
return expr();
|
||||
}
|
||||
|
||||
expr unary_not(const Iterator start_pos) const
|
||||
{
|
||||
switch (this->type) {
|
||||
case TYPE_BOOL :
|
||||
case TYPE_BOOL:
|
||||
return expr<Iterator>(! this->b(), start_pos, this->it_range.end());
|
||||
default:
|
||||
this->throw_exception("Cannot apply a not operator.");
|
||||
|
@ -549,6 +565,30 @@ namespace client
|
|||
}
|
||||
}
|
||||
|
||||
// Store the result into param1.
|
||||
// param3 is optional
|
||||
template<bool leading_zeros>
|
||||
static void digits(expr ¶m1, expr ¶m2, expr ¶m3)
|
||||
{
|
||||
throw_if_not_numeric(param1);
|
||||
if (param2.type != TYPE_INT)
|
||||
param2.throw_exception("digits: second parameter must be integer");
|
||||
bool has_decimals = param3.type != TYPE_EMPTY;
|
||||
if (has_decimals && param3.type != TYPE_INT)
|
||||
param3.throw_exception("digits: third parameter must be integer");
|
||||
|
||||
char buf[256];
|
||||
int ndigits = std::clamp(param2.as_i(), 0, 64);
|
||||
if (has_decimals) {
|
||||
// Format as double.
|
||||
int decimals = std::clamp(param3.as_i(), 0, 64);
|
||||
sprintf(buf, leading_zeros ? "%0*.*lf" : "%*.*lf", ndigits, decimals, param1.as_d());
|
||||
} else
|
||||
// Format as int.
|
||||
sprintf(buf, leading_zeros ? "%0*d" : "%*d", ndigits, param1.as_i_rounded());
|
||||
param1.set_s(buf);
|
||||
}
|
||||
|
||||
static void regex_op(expr &lhs, boost::iterator_range<Iterator> &rhs, char op)
|
||||
{
|
||||
const std::string *subject = nullptr;
|
||||
|
@ -930,6 +970,7 @@ namespace client
|
|||
{ "additive_expression", "Expecting an expression." },
|
||||
{ "multiplicative_expression", "Expecting an expression." },
|
||||
{ "unary_expression", "Expecting an expression." },
|
||||
{ "optional_parameter", "Expecting a closing brace or an optional parameter." },
|
||||
{ "scalar_variable_reference", "Expecting a scalar variable reference."},
|
||||
{ "variable_reference", "Expecting a variable reference."},
|
||||
{ "regular_expression", "Expecting a regular expression."}
|
||||
|
@ -1194,6 +1235,10 @@ namespace client
|
|||
{ out = value.unary_not(out.it_range.begin()); }
|
||||
static void to_int(expr<Iterator> &value, expr<Iterator> &out)
|
||||
{ out = value.unary_integer(out.it_range.begin()); }
|
||||
static void round(expr<Iterator> &value, expr<Iterator> &out)
|
||||
{ out = value.round(out.it_range.begin()); }
|
||||
// For indicating "no optional parameter".
|
||||
static void noexpr(expr<Iterator> &out) { out.reset(); }
|
||||
};
|
||||
unary_expression = iter_pos[px::bind(&FactorActions::set_start_pos, _1, _val)] >> (
|
||||
scalar_variable_reference(_r1) [ _val = _1 ]
|
||||
|
@ -1207,7 +1252,12 @@ namespace client
|
|||
[ px::bind(&expr<Iterator>::max, _val, _2) ]
|
||||
| (kw["random"] > '(' > conditional_expression(_r1) [_val = _1] > ',' > conditional_expression(_r1) > ')')
|
||||
[ px::bind(&MyContext::random<Iterator>, _r1, _val, _2) ]
|
||||
| (kw["int"] > '(' > unary_expression(_r1) > ')') [ px::bind(&FactorActions::to_int, _1, _val) ]
|
||||
| (kw["digits"] > '(' > conditional_expression(_r1) [_val = _1] > ',' > conditional_expression(_r1) > optional_parameter(_r1))
|
||||
[ px::bind(&expr<Iterator>::template digits<false>, _val, _2, _3) ]
|
||||
| (kw["zdigits"] > '(' > conditional_expression(_r1) [_val = _1] > ',' > conditional_expression(_r1) > optional_parameter(_r1))
|
||||
[ px::bind(&expr<Iterator>::template digits<true>, _val, _2, _3) ]
|
||||
| (kw["int"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::to_int, _1, _val) ]
|
||||
| (kw["round"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::round, _1, _val) ]
|
||||
| (strict_double > iter_pos) [ px::bind(&FactorActions::double_, _1, _2, _val) ]
|
||||
| (int_ > iter_pos) [ px::bind(&FactorActions::int_, _1, _2, _val) ]
|
||||
| (kw[bool_] > iter_pos) [ px::bind(&FactorActions::bool_, _1, _2, _val) ]
|
||||
|
@ -1216,6 +1266,12 @@ namespace client
|
|||
);
|
||||
unary_expression.name("unary_expression");
|
||||
|
||||
optional_parameter = iter_pos[px::bind(&FactorActions::set_start_pos, _1, _val)] >> (
|
||||
lit(')') [ px::bind(&FactorActions::noexpr, _val) ]
|
||||
| (lit(',') > conditional_expression(_r1) > ')') [ _val = _1 ]
|
||||
);
|
||||
optional_parameter.name("optional_parameter");
|
||||
|
||||
scalar_variable_reference =
|
||||
variable_reference(_r1)[_a=_1] >>
|
||||
(
|
||||
|
@ -1234,6 +1290,8 @@ namespace client
|
|||
|
||||
keywords.add
|
||||
("and")
|
||||
("digits")
|
||||
("zdigits")
|
||||
("if")
|
||||
("int")
|
||||
//("inf")
|
||||
|
@ -1244,6 +1302,7 @@ namespace client
|
|||
("min")
|
||||
("max")
|
||||
("random")
|
||||
("round")
|
||||
("not")
|
||||
("or")
|
||||
("true");
|
||||
|
@ -1266,6 +1325,7 @@ namespace client
|
|||
debug(additive_expression);
|
||||
debug(multiplicative_expression);
|
||||
debug(unary_expression);
|
||||
debug(optional_parameter);
|
||||
debug(scalar_variable_reference);
|
||||
debug(variable_reference);
|
||||
debug(regular_expression);
|
||||
|
@ -1303,6 +1363,8 @@ namespace client
|
|||
RuleExpression multiplicative_expression;
|
||||
// Number literals, functions, braced expressions, variable references, variable indexing references.
|
||||
RuleExpression unary_expression;
|
||||
// Accepting an optional parameter.
|
||||
RuleExpression optional_parameter;
|
||||
// Rule to capture a regular expression enclosed in //.
|
||||
qi::rule<Iterator, boost::iterator_range<Iterator>(), spirit_encoding::space_type> regular_expression;
|
||||
// Evaluate boolean expression into bool.
|
||||
|
|
|
@ -188,6 +188,61 @@ void PresetBundle::setup_directories()
|
|||
}
|
||||
}
|
||||
|
||||
// recursively copy all files and dirs in from_dir to to_dir
|
||||
static void copy_dir(const boost::filesystem::path& from_dir, const boost::filesystem::path& to_dir)
|
||||
{
|
||||
if(!boost::filesystem::is_directory(from_dir))
|
||||
return;
|
||||
// i assume to_dir.parent surely exists
|
||||
if (!boost::filesystem::is_directory(to_dir))
|
||||
boost::filesystem::create_directory(to_dir);
|
||||
for (auto& dir_entry : boost::filesystem::directory_iterator(from_dir)) {
|
||||
if (!boost::filesystem::is_directory(dir_entry.path())) {
|
||||
std::string em;
|
||||
CopyFileResult cfr = copy_file(dir_entry.path().string(), (to_dir / dir_entry.path().filename()).string(), em, false);
|
||||
if (cfr != SUCCESS) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Error when copying files from " << from_dir << " to " << to_dir << ": " << em;
|
||||
}
|
||||
} else {
|
||||
copy_dir(dir_entry.path(), to_dir / dir_entry.path().filename());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PresetBundle::copy_files(const std::string& from)
|
||||
{
|
||||
boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir());
|
||||
// list of searched paths based on current directory system in setup_directories()
|
||||
// do not copy cache and snapshots
|
||||
boost::filesystem::path from_data_dir = boost::filesystem::path(from);
|
||||
std::initializer_list<boost::filesystem::path> from_dirs= {
|
||||
from_data_dir / "vendor",
|
||||
from_data_dir / "shapes",
|
||||
#ifdef SLIC3R_PROFILE_USE_PRESETS_SUBDIR
|
||||
// Store the print/filament/printer presets into a "presets" directory.
|
||||
data_dir / "presets",
|
||||
data_dir / "presets" / "print",
|
||||
data_dir / "presets" / "filament",
|
||||
data_dir / "presets" / "sla_print",
|
||||
data_dir / "presets" / "sla_material",
|
||||
data_dir / "presets" / "printer",
|
||||
data_dir / "presets" / "physical_printer"
|
||||
#else
|
||||
// Store the print/filament/printer presets at the same location as the upstream Slic3r.
|
||||
from_data_dir / "print",
|
||||
from_data_dir / "filament",
|
||||
from_data_dir / "sla_print",
|
||||
from_data_dir / "sla_material",
|
||||
from_data_dir / "printer",
|
||||
from_data_dir / "physical_printer"
|
||||
#endif
|
||||
};
|
||||
// copy recursively all files
|
||||
for (const boost::filesystem::path& from_dir : from_dirs) {
|
||||
copy_dir(from_dir, data_dir / from_dir.filename());
|
||||
}
|
||||
}
|
||||
|
||||
PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule substitution_rule,
|
||||
const PresetPreferences& preferred_selection/* = PresetPreferences()*/)
|
||||
{
|
||||
|
@ -501,13 +556,15 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p
|
|||
auto printer_technology = printers.get_selected_preset().printer_technology();
|
||||
if (printer_technology == ptFFF && ! preferred_selection.filament.empty()) {
|
||||
std::string preferred_preset_name = get_preset_name_by_alias(Preset::Type::TYPE_FILAMENT, preferred_selection.filament);
|
||||
if (auto it = filaments.find_preset_internal(preferred_preset_name); it != filaments.end() && it->is_visible) {
|
||||
if (auto it = filaments.find_preset_internal(preferred_preset_name);
|
||||
it != filaments.end() && it->is_visible && it->is_compatible) {
|
||||
filaments.select_preset_by_name_strict(preferred_preset_name);
|
||||
this->filament_presets.front() = filaments.get_selected_preset_name();
|
||||
}
|
||||
} else if (printer_technology == ptSLA && ! preferred_selection.sla_material.empty()) {
|
||||
std::string preferred_preset_name = get_preset_name_by_alias(Preset::Type::TYPE_SLA_MATERIAL, preferred_selection.sla_material);
|
||||
if (auto it = sla_materials.find_preset_internal(preferred_preset_name); it != sla_materials.end() && it->is_visible)
|
||||
if (auto it = sla_materials.find_preset_internal(preferred_preset_name);
|
||||
it != sla_materials.end() && it->is_visible && it->is_compatible)
|
||||
sla_materials.select_preset_by_name_strict(preferred_preset_name);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ public:
|
|||
void reset(bool delete_files);
|
||||
|
||||
void setup_directories();
|
||||
void copy_files(const std::string& from);
|
||||
|
||||
struct PresetPreferences {
|
||||
std::string printer_model_id;// name of a preferred printer model
|
||||
|
|
|
@ -854,15 +854,14 @@ static PrintObjectRegions* generate_print_object_regions(
|
|||
for (int parent_region_id = int(layer_range.volume_regions.size()) - 1; parent_region_id >= 0; -- parent_region_id)
|
||||
if (const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[parent_region_id];
|
||||
parent_region.model_volume->is_model_part() || parent_region.model_volume->is_modifier()) {
|
||||
const PrintObjectRegions::BoundingBox *parent_bbox = find_volume_extents(layer_range, *parent_region.model_volume);
|
||||
assert(parent_bbox != nullptr);
|
||||
if (parent_bbox->intersects(*bbox))
|
||||
layer_range.volume_regions.push_back({
|
||||
&volume, parent_region_id,
|
||||
get_create_region(region_config_from_model_volume(parent_region.region->config(), nullptr, volume, num_extruders)),
|
||||
bbox
|
||||
});
|
||||
}
|
||||
const PrintObjectRegions::BoundingBox *parent_bbox = find_volume_extents(layer_range, *parent_region.model_volume);
|
||||
assert(parent_bbox != nullptr);
|
||||
if (parent_bbox->intersects(*bbox))
|
||||
// Only create new region for a modifier, which actually modifies config of it's parent.
|
||||
if (PrintRegionConfig config = region_config_from_model_volume(parent_region.region->config(), nullptr, volume, num_extruders);
|
||||
config != parent_region.region->config())
|
||||
layer_range.volume_regions.push_back({ &volume, parent_region_id, get_create_region(std::move(config)), bbox });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,14 +26,17 @@ void PrintBase::update_object_placeholders(DynamicConfig &config, const std::str
|
|||
// get the first input file name
|
||||
std::string input_file;
|
||||
std::vector<std::string> v_scale;
|
||||
int num_objects = 0;
|
||||
int num_instances = 0;
|
||||
for (const ModelObject *model_object : m_model.objects) {
|
||||
ModelInstance *printable = nullptr;
|
||||
for (ModelInstance *model_instance : model_object->instances)
|
||||
if (model_instance->is_printable()) {
|
||||
printable = model_instance;
|
||||
break;
|
||||
++ num_instances;
|
||||
}
|
||||
if (printable) {
|
||||
++ num_objects;
|
||||
// CHECK_ME -> Is the following correct ?
|
||||
v_scale.push_back("x:" + boost::lexical_cast<std::string>(printable->get_scaling_factor(X) * 100) +
|
||||
"% y:" + boost::lexical_cast<std::string>(printable->get_scaling_factor(Y) * 100) +
|
||||
|
@ -43,6 +46,9 @@ void PrintBase::update_object_placeholders(DynamicConfig &config, const std::str
|
|||
}
|
||||
}
|
||||
|
||||
config.set_key_value("num_objects", new ConfigOptionInt(num_objects));
|
||||
config.set_key_value("num_instances", new ConfigOptionInt(num_instances));
|
||||
|
||||
config.set_key_value("scale", new ConfigOptionStrings(v_scale));
|
||||
if (! input_file.empty()) {
|
||||
// get basename with and without suffix
|
||||
|
|
|
@ -2284,6 +2284,7 @@ void PrintObject::project_and_append_custom_facets(
|
|||
seam, out);
|
||||
else {
|
||||
std::vector<Polygons> projected;
|
||||
// Support blockers or enforcers. Project downward facing painted areas upwards to their respective slicing plane.
|
||||
slice_mesh_slabs(custom_facets, zs_from_layers(this->layers()), this->trafo_centered() * mv->get_matrix(), nullptr, &projected, [](){});
|
||||
// Merge these projections with the output, layer by layer.
|
||||
assert(! projected.empty());
|
||||
|
|
|
@ -559,7 +559,7 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po)
|
|||
|
||||
if(po.m_config.supports_enable.getBool() || po.m_config.pad_enable.getBool())
|
||||
{
|
||||
po.m_supportdata.reset(new SLAPrintObject::SupportData(mesh));
|
||||
po.m_supportdata.reset(new SLAPrintObject::SupportData(po.get_mesh_to_print()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -570,10 +570,8 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po)
|
|||
// If supports are disabled, we can skip the model scan.
|
||||
if(!po.m_config.supports_enable.getBool()) return;
|
||||
|
||||
const TriangleMesh &mesh = po.get_mesh_to_slice();
|
||||
|
||||
if (!po.m_supportdata)
|
||||
po.m_supportdata.reset(new SLAPrintObject::SupportData(mesh));
|
||||
po.m_supportdata.reset(new SLAPrintObject::SupportData(po.get_mesh_to_print()));
|
||||
|
||||
const ModelObject& mo = *po.m_model_object;
|
||||
|
||||
|
|
|
@ -383,7 +383,9 @@ PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object
|
|||
|
||||
SupportMaterialPattern support_pattern = m_object_config->support_material_pattern;
|
||||
m_support_params.with_sheath = m_object_config->support_material_with_sheath;
|
||||
m_support_params.base_fill_pattern = support_pattern == smpHoneycomb ? ipHoneycomb : (m_support_params.support_density > 0.95 ? ipRectilinear : ipSupportBase);
|
||||
m_support_params.base_fill_pattern =
|
||||
support_pattern == smpHoneycomb ? ipHoneycomb :
|
||||
m_support_params.support_density > 0.95 || m_support_params.with_sheath ? ipRectilinear : ipSupportBase;
|
||||
m_support_params.interface_fill_pattern = (m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase);
|
||||
m_support_params.contact_fill_pattern =
|
||||
(m_object_config->support_material_interface_pattern == smipAuto && m_slicing_params.soluble_interface) ||
|
||||
|
@ -1645,26 +1647,29 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
|
|||
polygons_append(contact_polygons, diff_polygons);
|
||||
} // for each layer.region
|
||||
|
||||
if (has_enforcer) {
|
||||
// Enforce supports (as if with 90 degrees of slope) for the regions covered by the enforcer meshes.
|
||||
#ifdef SLIC3R_DEBUG
|
||||
ExPolygons enforcers_united = union_ex(annotations.enforcers_layers[layer_id]);
|
||||
#endif // SLIC3R_DEBUG
|
||||
enforcer_polygons = diff(intersection(layer.lslices, annotations.enforcers_layers[layer_id]),
|
||||
// Inflate just a tiny bit to avoid intersection of the overhang areas with the object.
|
||||
expand(lower_layer_polygons, 0.05f * no_interface_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS));
|
||||
#ifdef SLIC3R_DEBUG
|
||||
SVG::export_expolygons(debug_out_path("support-top-contacts-enforcers-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z),
|
||||
{ { layer.lslices, { "layer.lslices", "gray", 0.2f } },
|
||||
{ { union_ex(lower_layer_polygons) }, { "lower_layer_polygons", "green", 0.5f } },
|
||||
{ enforcers_united, { "enforcers", "blue", 0.5f } },
|
||||
{ { union_safety_offset_ex(enforcer_polygons) }, { "new_contacts", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
|
||||
#endif /* SLIC3R_DEBUG */
|
||||
polygons_append(overhang_polygons, enforcer_polygons);
|
||||
slices_margin_update(std::min(lower_layer_offset, float(scale_(gap_xy))), no_interface_offset);
|
||||
polygons_append(contact_polygons, diff(enforcer_polygons, slices_margin.all_polygons.empty() ? slices_margin.polygons : slices_margin.all_polygons));
|
||||
if (has_enforcer)
|
||||
if (const Polygons &enforcer_polygons_src = annotations.enforcers_layers[layer_id]; ! enforcer_polygons_src.empty()) {
|
||||
// Enforce supports (as if with 90 degrees of slope) for the regions covered by the enforcer meshes.
|
||||
#ifdef SLIC3R_DEBUG
|
||||
ExPolygons enforcers_united = union_ex(enforcer_polygons_src);
|
||||
#endif // SLIC3R_DEBUG
|
||||
enforcer_polygons = diff(intersection(layer.lslices, enforcer_polygons_src),
|
||||
// Inflate just a tiny bit to avoid intersection of the overhang areas with the object.
|
||||
expand(lower_layer_polygons, 0.05f * no_interface_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS));
|
||||
#ifdef SLIC3R_DEBUG
|
||||
SVG::export_expolygons(debug_out_path("support-top-contacts-enforcers-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z),
|
||||
{ { layer.lslices, { "layer.lslices", "gray", 0.2f } },
|
||||
{ { union_ex(lower_layer_polygons) }, { "lower_layer_polygons", "green", 0.5f } },
|
||||
{ enforcers_united, { "enforcers", "blue", 0.5f } },
|
||||
{ { union_safety_offset_ex(enforcer_polygons) }, { "new_contacts", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
|
||||
#endif /* SLIC3R_DEBUG */
|
||||
if (! enforcer_polygons.empty()) {
|
||||
polygons_append(overhang_polygons, enforcer_polygons);
|
||||
slices_margin_update(std::min(lower_layer_offset, float(scale_(gap_xy))), no_interface_offset);
|
||||
polygons_append(contact_polygons, diff(enforcer_polygons, slices_margin.all_polygons.empty() ? slices_margin.polygons : slices_margin.all_polygons));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_tuple(std::move(overhang_polygons), std::move(contact_polygons), std::move(enforcer_polygons), no_interface_offset);
|
||||
}
|
||||
|
@ -4057,7 +4062,8 @@ void PrintObjectSupportMaterial::generate_toolpaths(
|
|||
// Pointer to the 1st layer interface filler.
|
||||
auto filler_first_layer = filler_first_layer_ptr ? filler_first_layer_ptr.get() : filler_interface.get();
|
||||
// Filler for the base interface (to be used for soluble interface / non soluble base, to produce non soluble interface layer below soluble interface layer).
|
||||
auto filler_base_interface = std::unique_ptr<Fill>(base_interface_layers.empty() ? nullptr : Fill::new_from_type(m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase));
|
||||
auto filler_base_interface = std::unique_ptr<Fill>(base_interface_layers.empty() ? nullptr :
|
||||
Fill::new_from_type(m_support_params.interface_density > 0.95 || m_support_params.with_sheath ? ipRectilinear : ipSupportBase));
|
||||
auto filler_support = std::unique_ptr<Fill>(Fill::new_from_type(m_support_params.base_fill_pattern));
|
||||
filler_interface->set_bounding_box(bbox_object);
|
||||
if (filler_first_layer_ptr)
|
||||
|
|
|
@ -332,7 +332,7 @@ void slice_facet_at_zs(
|
|||
if (min_z != max_z && slice_facet(*it, vertices, indices, edge_ids, idx_vertex_lowest, false, il) == FacetSliceType::Slicing) {
|
||||
assert(il.edge_type != IntersectionLine::FacetEdgeType::Horizontal);
|
||||
size_t slice_id = it - zs.begin();
|
||||
boost::lock_guard<std::mutex> l(lines_mutex[slice_id % 64]);
|
||||
boost::lock_guard<std::mutex> l(lines_mutex[slice_id % lines_mutex.size()]);
|
||||
lines[slice_id].emplace_back(il);
|
||||
}
|
||||
}
|
||||
|
@ -446,7 +446,7 @@ void slice_facet_with_slabs(
|
|||
auto emit_slab_edge = [&lines, &lines_mutex](IntersectionLine il, size_t slab_id, bool reverse) {
|
||||
if (reverse)
|
||||
il.reverse();
|
||||
boost::lock_guard<std::mutex> l(lines_mutex[(slab_id + 32) >> 6]);
|
||||
boost::lock_guard<std::mutex> l(lines_mutex[(slab_id + lines_mutex.size() / 2) % lines_mutex.size()]);
|
||||
lines.between_slices[slab_id].emplace_back(il);
|
||||
};
|
||||
|
||||
|
@ -458,23 +458,33 @@ void slice_facet_with_slabs(
|
|||
// Slicing a horizontal triangle with a slicing plane. The triangle has to be upwards facing for ProjectionFromTop
|
||||
// and downwards facing for ! ProjectionFromTop.
|
||||
assert(min_layer != max_layer);
|
||||
size_t line_id = min_layer - zs.begin();
|
||||
for (int iedge = 0; iedge < 3; ++ iedge)
|
||||
if (facet_neighbors(iedge) == -1) {
|
||||
int i = iedge;
|
||||
int j = next_idx_modulo(i, 3);
|
||||
assert(vertices[i].z() == zs[line_id]);
|
||||
assert(vertices[j].z() == zs[line_id]);
|
||||
IntersectionLine il {
|
||||
{ to_2d(vertices[i]).cast<coord_t>(), to_2d(vertices[j]).cast<coord_t>() },
|
||||
indices(i), indices(j), -1, -1,
|
||||
ProjectionFromTop ? IntersectionLine::FacetEdgeType::Bottom : IntersectionLine::FacetEdgeType::Top
|
||||
};
|
||||
// Don't flip the FacetEdgeType::Top edge, it will be flipped when chaining.
|
||||
// if (! ProjectionFromTop) il.reverse();
|
||||
boost::lock_guard<std::mutex> l(lines_mutex[line_id >> 6]);
|
||||
lines.at_slice[line_id].emplace_back(il);
|
||||
}
|
||||
// Slicing plane with which the triangle is coplanar.
|
||||
size_t slice_id = min_layer - zs.begin();
|
||||
#if 0
|
||||
// Project the coplanar bottom facing triangles to their slicing plane for both top and bottom facing surfaces.
|
||||
// This behavior is different from slice_mesh() / slice_mesh_ex(), which do not slice bottom facing faces exactly on slicing plane.
|
||||
size_t line_id = slice_id;
|
||||
#else
|
||||
// Project the coplanar bottom facing triangles to the plane above the slicing plane to match the behavior of slice_mesh() / slice_mesh_ex(),
|
||||
// where the slicing plane slices the top facing surfaces, but misses the bottom facing surfaces.
|
||||
if (size_t line_id = ProjectionFromTop ? slice_id : slice_id + 1; ProjectionFromTop || line_id < lines.at_slice.size())
|
||||
#endif
|
||||
for (int iedge = 0; iedge < 3; ++ iedge)
|
||||
if (facet_neighbors(iedge) == -1) {
|
||||
int i = iedge;
|
||||
int j = next_idx_modulo(i, 3);
|
||||
assert(vertices[i].z() == zs[slice_id]);
|
||||
assert(vertices[j].z() == zs[slice_id]);
|
||||
IntersectionLine il {
|
||||
{ to_2d(vertices[i]).cast<coord_t>(), to_2d(vertices[j]).cast<coord_t>() },
|
||||
indices(i), indices(j), -1, -1,
|
||||
ProjectionFromTop ? IntersectionLine::FacetEdgeType::Bottom : IntersectionLine::FacetEdgeType::Top
|
||||
};
|
||||
// Don't flip the FacetEdgeType::Top edge, it will be flipped when chaining.
|
||||
// if (! ProjectionFromTop) il.reverse();
|
||||
boost::lock_guard<std::mutex> l(lines_mutex[line_id % lines_mutex.size()]);
|
||||
lines.at_slice[line_id].emplace_back(il);
|
||||
}
|
||||
} else {
|
||||
// Triangle is completely between two slicing planes, the triangle may or may not be horizontal, which
|
||||
// does not matter for the processing of such a triangle.
|
||||
|
@ -539,6 +549,7 @@ void slice_facet_with_slabs(
|
|||
assert(il.b_id == indices(next_idx_modulo(edge_id, 3)));
|
||||
} else {
|
||||
// The edge is oriented CW along the face perimeter.
|
||||
assert(type == FacetSliceType::Slicing);
|
||||
assert(il.edge_type == IntersectionLine::FacetEdgeType::Top);
|
||||
edge_id = il.b_id == indices(0) ? 0 : il.b_id == indices(1) ? 1 : 2;
|
||||
assert(il.b_id == indices(edge_id));
|
||||
|
@ -555,8 +566,11 @@ void slice_facet_with_slabs(
|
|||
int num_on_plane = (mesh_vertices[neighbor(0)].z() == z) + (mesh_vertices[neighbor(1)].z() == z) + (mesh_vertices[neighbor(2)].z() == z);
|
||||
assert(num_on_plane == 2 || num_on_plane == 3);
|
||||
#endif // NDEBUG
|
||||
#if 0
|
||||
if (mesh_vertices[neighbor(0)].z() == z && mesh_vertices[neighbor(1)].z() == z && mesh_vertices[neighbor(2)].z() == z) {
|
||||
// The neighbor triangle is horizontal.
|
||||
// Assign the horizontal projections to slicing planes differently from the usual triangle mesh slicing:
|
||||
// Slicing plane slices top surfaces when projecting from top, it slices bottom surfaces when projecting from bottom.
|
||||
// Is the corner convex or concave?
|
||||
if (il.edge_type == (ProjectionFromTop ? IntersectionLine::FacetEdgeType::Top : IntersectionLine::FacetEdgeType::Bottom)) {
|
||||
// Convex corner. Add this edge to both slabs, the edge is a boundary edge of both the projection patch below and
|
||||
|
@ -567,7 +581,12 @@ void slice_facet_with_slabs(
|
|||
// Concave corner. Ignore this edge, it is internal to the projection patch.
|
||||
type = FacetSliceType::Cutting;
|
||||
}
|
||||
} else if (il.edge_type == IntersectionLine::FacetEdgeType::Top) {
|
||||
} else
|
||||
#else
|
||||
// Project the coplanar bottom facing triangles to the plane above the slicing plane to match the behavior of slice_mesh() / slice_mesh_ex(),
|
||||
// where the slicing plane slices the top facing surfaces, but misses the bottom facing surfaces.
|
||||
#endif
|
||||
if (il.edge_type == IntersectionLine::FacetEdgeType::Top) {
|
||||
// Indicate that the edge belongs to both the slab below and above the plane.
|
||||
assert(type == FacetSliceType::Slicing);
|
||||
il.edge_type = IntersectionLine::FacetEdgeType::TopBottom;
|
||||
|
@ -582,7 +601,7 @@ void slice_facet_with_slabs(
|
|||
if (! ProjectionFromTop)
|
||||
il.reverse();
|
||||
size_t line_id = it - zs.begin();
|
||||
boost::lock_guard<std::mutex> l(lines_mutex[line_id >> 6]);
|
||||
boost::lock_guard<std::mutex> l(lines_mutex[line_id % lines_mutex.size()]);
|
||||
lines.at_slice[line_id].emplace_back(il);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,6 +46,17 @@ struct MeshSlicingParamsEx : public MeshSlicingParams
|
|||
double resolution { 0 };
|
||||
};
|
||||
|
||||
// All the following slicing functions shall produce consistent results with the same mesh, same transformation matrix and slicing parameters.
|
||||
// Namely, slice_mesh_slabs() shall produce consistent results with slice_mesh() and slice_mesh_ex() in the sense, that projections made by
|
||||
// slice_mesh_slabs() shall fall onto slicing planes produced by slice_mesh().
|
||||
//
|
||||
// If a slicing plane slices a horizontal face of a mesh exactly,
|
||||
// an upward facing horizontal face is is considered on slicing plane,
|
||||
// while a downward facing horizontal face is considered not on slicing plane.
|
||||
//
|
||||
// slice_mesh_slabs() thus projects an upward facing horizontal slice to the slicing plane,
|
||||
// while slice_mesh_slabs() projects a downward facing horizontal slice to the slicing plane above if it exists.
|
||||
|
||||
std::vector<Polygons> slice_mesh(
|
||||
const indexed_triangle_set &mesh,
|
||||
const std::vector<float> &zs,
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
#include "libslic3r/Polygon.hpp"
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "libslic3r/Geometry.hpp"
|
||||
#include "libslic3r/Geometry/Circle.hpp"
|
||||
#include "libslic3r/Tesselate.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ void ConfigManipulation::toggle_field(const std::string& opt_key, const bool tog
|
|||
cb_toggle_field(opt_key, toggle, opt_index);
|
||||
}
|
||||
|
||||
void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, const bool is_global_config, bool set_support_material_overhangs_queried)
|
||||
void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, const bool is_global_config)
|
||||
{
|
||||
// #ys_FIXME_to_delete
|
||||
//! Temporary workaround for the correct updates of the TextCtrl (like "layer_height"):
|
||||
|
@ -160,12 +160,10 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
|
|||
apply(config, &new_conf);
|
||||
}
|
||||
|
||||
support_material_overhangs_queried = set_support_material_overhangs_queried;
|
||||
|
||||
if (config->opt_bool("support_material")) {
|
||||
// Ask only once.
|
||||
if (!support_material_overhangs_queried) {
|
||||
support_material_overhangs_queried = true;
|
||||
if (!m_support_material_overhangs_queried) {
|
||||
m_support_material_overhangs_queried = true;
|
||||
if (!config->opt_bool("overhangs")/* != 1*/) {
|
||||
wxString msg_text = _(L("Supports work better, if the following feature is enabled:\n"
|
||||
"- Detect bridging perimeters"));
|
||||
|
@ -184,7 +182,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
|
|||
}
|
||||
}
|
||||
else {
|
||||
support_material_overhangs_queried = false;
|
||||
m_support_material_overhangs_queried = false;
|
||||
}
|
||||
|
||||
if (config->option<ConfigOptionPercent>("fill_density")->value == 100) {
|
||||
|
|
|
@ -20,7 +20,9 @@ namespace GUI {
|
|||
class ConfigManipulation
|
||||
{
|
||||
bool is_msg_dlg_already_exist{ false };
|
||||
bool support_material_overhangs_queried{ false };
|
||||
|
||||
bool m_support_material_overhangs_queried{false};
|
||||
bool m_is_initialized_support_material_overhangs_queried{ false };
|
||||
|
||||
// function to loading of changed configuration
|
||||
std::function<void()> load_config = nullptr;
|
||||
|
@ -50,12 +52,19 @@ public:
|
|||
void toggle_field(const std::string& field_key, const bool toggle, int opt_index = -1);
|
||||
|
||||
// FFF print
|
||||
void update_print_fff_config(DynamicPrintConfig* config, const bool is_global_config = false, bool set_support_material_overhangs_queried = false);
|
||||
void update_print_fff_config(DynamicPrintConfig* config, const bool is_global_config = false);
|
||||
void toggle_print_fff_options(DynamicPrintConfig* config);
|
||||
|
||||
// SLA print
|
||||
void update_print_sla_config(DynamicPrintConfig* config, const bool is_global_config = false);
|
||||
void toggle_print_sla_options(DynamicPrintConfig* config);
|
||||
|
||||
bool is_initialized_support_material_overhangs_queried() { return m_is_initialized_support_material_overhangs_queried; }
|
||||
void initialize_support_material_overhangs_queried(bool queried)
|
||||
{
|
||||
m_is_initialized_support_material_overhangs_queried = true;
|
||||
m_support_material_overhangs_queried = queried;
|
||||
}
|
||||
};
|
||||
|
||||
} // GUI
|
||||
|
|
|
@ -3895,7 +3895,7 @@ void GLCanvas3D::update_sequential_clearance()
|
|||
bool GLCanvas3D::is_object_sinking(int object_idx) const
|
||||
{
|
||||
for (const GLVolume* v : m_volumes.volumes) {
|
||||
if (v->object_idx() == object_idx && v->is_sinking())
|
||||
if (v->object_idx() == object_idx && (v->is_sinking() || (!v->is_modifier && v->is_below_printbed())))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
@ -673,8 +673,7 @@ void GUI_App::post_init()
|
|||
// to popup a modal dialog on start without screwing combo boxes.
|
||||
// This is ugly but I honestly found no better way to do it.
|
||||
// Neither wxShowEvent nor wxWindowCreateEvent work reliably.
|
||||
assert(this->preset_updater); // FIXME Following condition is probably not neccessary.
|
||||
if (this->preset_updater) {
|
||||
if (this->preset_updater) { // G-Code Viewer does not initialize preset_updater.
|
||||
this->check_updates(false);
|
||||
CallAfter([this] {
|
||||
bool cw_showed = this->config_wizard_startup();
|
||||
|
@ -744,6 +743,25 @@ bool GUI_App::init_opengl()
|
|||
#endif
|
||||
}
|
||||
|
||||
// gets path to PrusaSlicer.ini, returns semver from first line comment
|
||||
static boost::optional<Semver> parse_semver_from_ini(std::string path)
|
||||
{
|
||||
std::ifstream stream(path);
|
||||
std::stringstream buffer;
|
||||
buffer << stream.rdbuf();
|
||||
std::string body = buffer.str();
|
||||
size_t end_line = body.find_first_of("\n\r");
|
||||
body.resize(end_line);
|
||||
size_t start = body.find("PrusaSlicer ");
|
||||
if (start == std::string::npos)
|
||||
return boost::none;
|
||||
body = body.substr(start + 12);
|
||||
size_t end = body.find_first_of(" \n\r");
|
||||
if (end < body.size())
|
||||
body.resize(end);
|
||||
return Semver::parse(body);
|
||||
}
|
||||
|
||||
void GUI_App::init_app_config()
|
||||
{
|
||||
// Profiles for the alpha are stored into the PrusaSlicer-alpha directory to not mix with the current release.
|
||||
|
@ -792,9 +810,110 @@ void GUI_App::init_app_config()
|
|||
"\n\n" + app_config->config_path() + "\n\n" + error);
|
||||
}
|
||||
}
|
||||
// Save orig_version here, so its empty if no app_config existed before this run.
|
||||
m_last_config_version = app_config->orig_version();//parse_semver_from_ini(app_config->config_path());
|
||||
}
|
||||
}
|
||||
|
||||
// returns true if found newer version and user agreed to use it
|
||||
bool GUI_App::check_older_app_config(Semver current_version, bool backup)
|
||||
{
|
||||
// find other version app config (alpha / beta / release)
|
||||
std::string config_path = app_config->config_path();
|
||||
boost::filesystem::path parent_file_path(config_path);
|
||||
std::string filename = parent_file_path.filename().string();
|
||||
parent_file_path.remove_filename().remove_filename();
|
||||
|
||||
std::vector<boost::filesystem::path> candidates;
|
||||
|
||||
if (SLIC3R_APP_KEY "-alpha" != GetAppName()) candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY "-alpha" / filename);
|
||||
if (SLIC3R_APP_KEY "-beta" != GetAppName()) candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY "-beta" / filename);
|
||||
if (SLIC3R_APP_KEY != GetAppName()) candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY / filename);
|
||||
|
||||
Semver last_semver = current_version;
|
||||
for (const auto& candidate : candidates) {
|
||||
if (boost::filesystem::exists(candidate)) {
|
||||
// parse
|
||||
boost::optional<Semver>other_semver = parse_semver_from_ini(candidate.string());
|
||||
if (other_semver && *other_semver > last_semver) {
|
||||
last_semver = *other_semver;
|
||||
m_older_data_dir_path = candidate.parent_path().string();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m_older_data_dir_path.empty())
|
||||
return false;
|
||||
BOOST_LOG_TRIVIAL(info) << "last app config file used: " << m_older_data_dir_path;
|
||||
// ask about using older data folder
|
||||
wxRichMessageDialog msg(nullptr, backup ?
|
||||
wxString::Format(_L("PrusaSlicer detected another configuration folder at %s."
|
||||
"\nIts version is %s."
|
||||
"\nLast version you used in current configuration folder is %s."
|
||||
"\nPlease note that PrusaSlicer uses different folders to save configuration of alpha, beta and full release versions."
|
||||
"\nWould you like to copy found configuration to your current configuration folder?"
|
||||
|
||||
"\n\nIf you select yes, PrusaSlicer will copy all profiles and other files from found folder to the current one. Overwriting any existing file with matching name."
|
||||
"\nIf you select no, you will continue with current configuration.")
|
||||
, m_older_data_dir_path, last_semver.to_string(), current_version.to_string())
|
||||
: wxString::Format(_L("PrusaSlicer detected another configuration folder at %s."
|
||||
"\nIts version is %s."
|
||||
"\nThere is no configuration file in current configuration folder."
|
||||
"\nPlease note that PrusaSlicer uses different folders to save configuration of alpha, beta and full release versions."
|
||||
"\nWould you like to copy found configuration to your current configuration folder?"
|
||||
|
||||
"\n\nIf you select yes, PrusaSlicer will copy all profiles and other files from found folder to the current one."
|
||||
"\nIf you select no, you will start with clean installation with configuration wizard.")
|
||||
, m_older_data_dir_path, last_semver.to_string())
|
||||
, _L("PrusaSlicer"), wxICON_QUESTION | wxYES_NO);
|
||||
if (msg.ShowModal() == wxID_YES) {
|
||||
std::string snapshot_id;
|
||||
if (backup) {
|
||||
// configuration snapshot
|
||||
std::string comment;
|
||||
if (const Config::Snapshot* snapshot = Config::take_config_snapshot_report_error(
|
||||
*app_config,
|
||||
Config::Snapshot::SNAPSHOT_USER,
|
||||
comment);
|
||||
snapshot != nullptr)
|
||||
// Is thos correct? Save snapshot id for later, when new app config is loaded.
|
||||
snapshot_id = snapshot->id;
|
||||
else
|
||||
BOOST_LOG_TRIVIAL(error) << "Failed to take congiguration snapshot: ";
|
||||
}
|
||||
|
||||
// This will tell later (when config folder structure is sure to exists) to copy files from m_older_data_dir_path
|
||||
m_init_app_config_from_older = true;
|
||||
// load app config from older file
|
||||
app_config->set_loading_path((boost::filesystem::path(m_older_data_dir_path) / filename).string());
|
||||
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.
|
||||
if (is_editor()) {
|
||||
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" + app_config->config_path() + "\n\n" + error);
|
||||
}
|
||||
else {
|
||||
throw Slic3r::RuntimeError(
|
||||
_u8L("Error parsing PrusaGCodeViewer config file, it is probably corrupted. "
|
||||
"Try to manually delete the file to recover from the error.") +
|
||||
"\n\n" + app_config->config_path() + "\n\n" + error);
|
||||
}
|
||||
}
|
||||
if (!snapshot_id.empty())
|
||||
app_config->set("on_snapshot", snapshot_id);
|
||||
m_app_conf_exists = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void GUI_App::copy_older_config()
|
||||
{
|
||||
preset_bundle->copy_files(m_older_data_dir_path);
|
||||
}
|
||||
|
||||
void GUI_App::init_single_instance_checker(const std::string &name, const std::string &path)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(debug) << "init wx instance checker " << name << " "<< path;
|
||||
|
@ -813,6 +932,29 @@ bool GUI_App::OnInit()
|
|||
|
||||
bool GUI_App::on_init_inner()
|
||||
{
|
||||
// win32 build on win64 and viceversa
|
||||
#ifdef _WIN64
|
||||
if (wxPlatformInfo::Get().GetArchName().substr(0, 2) == "") {
|
||||
wxRichMessageDialog dlg(nullptr,
|
||||
_L("You have started PrusaSlicer for 64-bit architecture on 32-bit system."
|
||||
"\nPlease download and install correct version at https://www.prusa3d.cz/prusaslicer/."
|
||||
"\nDo you wish to continue?"),
|
||||
"PrusaSlicer", wxICON_QUESTION | wxYES_NO);
|
||||
if (dlg.ShowModal() != wxID_YES)
|
||||
return false;
|
||||
}
|
||||
#elif _WIN32
|
||||
if (wxPlatformInfo::Get().GetArchName().substr(0, 2) == "64") {
|
||||
wxRichMessageDialog dlg(nullptr,
|
||||
_L("You have started PrusaSlicer for 32-bit architecture on 64-bit system."
|
||||
"\nPlease download and install correct version at https://www.prusa3d.cz/prusaslicer/."
|
||||
"\nDo you wish to continue?"),
|
||||
"PrusaSlicer", wxICON_QUESTION | wxYES_NO);
|
||||
if (dlg.ShowModal() != wxID_YES)
|
||||
return false;
|
||||
}
|
||||
#endif // _WIN64
|
||||
|
||||
// Forcing back menu icons under gtk2 and gtk3. Solution is based on:
|
||||
// https://docs.gtk.org/gtk3/class.Settings.html
|
||||
// see also https://docs.wxwidgets.org/3.0/classwx_menu_item.html#a2b5d6bcb820b992b1e4709facbf6d4fb
|
||||
|
@ -862,6 +1004,13 @@ bool GUI_App::on_init_inner()
|
|||
}
|
||||
}
|
||||
|
||||
if (m_last_config_version) {
|
||||
if (*m_last_config_version < *Semver::parse(SLIC3R_VERSION))
|
||||
check_older_app_config(*m_last_config_version, true);
|
||||
} else {
|
||||
check_older_app_config(Semver(), false);
|
||||
}
|
||||
|
||||
app_config->set("version", SLIC3R_VERSION);
|
||||
app_config->save();
|
||||
|
||||
|
@ -900,12 +1049,18 @@ bool GUI_App::on_init_inner()
|
|||
scrn->SetText(_L("Loading configuration")+ dots);
|
||||
}
|
||||
|
||||
|
||||
|
||||
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 (m_init_app_config_from_older)
|
||||
copy_older_config();
|
||||
|
||||
if (is_editor()) {
|
||||
#ifdef __WXMSW__
|
||||
if (app_config->get("associate_3mf") == "1")
|
||||
|
@ -1577,7 +1732,7 @@ static const wxLanguageInfo* linux_get_existing_locale_language(const wxLanguage
|
|||
}
|
||||
#endif
|
||||
|
||||
static int GetSingleChoiceIndex(const wxString& message,
|
||||
int GUI_App::GetSingleChoiceIndex(const wxString& message,
|
||||
const wxString& caption,
|
||||
const wxArrayString& choices,
|
||||
int initialSelection)
|
||||
|
|
|
@ -326,6 +326,7 @@ public:
|
|||
|
||||
bool is_gl_version_greater_or_equal_to(unsigned int major, unsigned int minor) const { return m_opengl_mgr.get_gl_info().is_version_greater_or_equal_to(major, minor); }
|
||||
bool is_glsl_version_greater_or_equal_to(unsigned int major, unsigned int minor) const { return m_opengl_mgr.get_gl_info().is_glsl_version_greater_or_equal_to(major, minor); }
|
||||
int GetSingleChoiceIndex(const wxString& message, const wxString& caption, const wxArrayString& choices, int initialSelection);
|
||||
|
||||
#ifdef __WXMSW__
|
||||
void associate_3mf_files();
|
||||
|
@ -336,6 +337,8 @@ public:
|
|||
private:
|
||||
bool on_init_inner();
|
||||
void init_app_config();
|
||||
bool check_older_app_config(Semver current_version, bool backup);
|
||||
void copy_older_config();
|
||||
void window_pos_save(wxTopLevelWindow* window, const std::string &name);
|
||||
void window_pos_restore(wxTopLevelWindow* window, const std::string &name, bool default_maximized = false);
|
||||
void window_pos_sanitize(wxTopLevelWindow* window);
|
||||
|
@ -343,6 +346,10 @@ private:
|
|||
|
||||
bool config_wizard_startup();
|
||||
void check_updates(const bool verbose);
|
||||
|
||||
bool m_init_app_config_from_older { false };
|
||||
std::string m_older_data_dir_path;
|
||||
boost::optional<Semver> m_last_config_version;
|
||||
};
|
||||
|
||||
DECLARE_APP(GUI_App)
|
||||
|
|
|
@ -3789,7 +3789,7 @@ void ObjectList::change_part_type()
|
|||
}
|
||||
|
||||
const wxString names[] = { _L("Part"), _L("Negative Volume"), _L("Modifier"), _L("Support Blocker"), _L("Support Enforcer") };
|
||||
auto new_type = ModelVolumeType(wxGetSingleChoiceIndex(_L("Type:"), _L("Select type of part"), wxArrayString(5, names), int(type)));
|
||||
auto new_type = ModelVolumeType(wxGetApp().GetSingleChoiceIndex(_L("Type:"), _L("Select type of part"), wxArrayString(5, names), int(type)));
|
||||
|
||||
if (new_type == type || new_type == ModelVolumeType::INVALID)
|
||||
return;
|
||||
|
|
|
@ -18,9 +18,6 @@ protected:
|
|||
std::string on_get_name() const override;
|
||||
|
||||
wxString handle_snapshot_action_name(bool shift_down, Button button_down) const override;
|
||||
|
||||
std::string get_gizmo_entering_text() const override { return _u8L("Entering Paint-on supports"); }
|
||||
std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Paint-on supports"); }
|
||||
std::string get_action_snapshot_name() override { return _u8L("Paint-on supports editing"); }
|
||||
|
||||
|
||||
|
|
|
@ -111,9 +111,6 @@ protected:
|
|||
bool on_is_activable() const override;
|
||||
|
||||
wxString handle_snapshot_action_name(bool shift_down, Button button_down) const override;
|
||||
|
||||
std::string get_gizmo_entering_text() const override { return _u8L("Entering Multimaterial painting"); }
|
||||
std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Multimaterial painting"); }
|
||||
std::string get_action_snapshot_name() override { return _u8L("Multimaterial painting editing"); }
|
||||
|
||||
size_t m_first_selected_extruder_idx = 0;
|
||||
|
|
|
@ -230,9 +230,6 @@ protected:
|
|||
|
||||
virtual wxString handle_snapshot_action_name(bool shift_down, Button button_down) const = 0;
|
||||
|
||||
virtual std::string get_gizmo_entering_text() const = 0;
|
||||
virtual std::string get_gizmo_leaving_text() const = 0;
|
||||
|
||||
friend class ::Slic3r::GUI::GLGizmoMmuSegmentation;
|
||||
};
|
||||
|
||||
|
|
|
@ -20,8 +20,6 @@ protected:
|
|||
|
||||
wxString handle_snapshot_action_name(bool shift_down, Button button_down) const override;
|
||||
|
||||
std::string get_gizmo_entering_text() const override { return _u8L("Entering Seam painting"); }
|
||||
std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Seam painting"); }
|
||||
std::string get_action_snapshot_name() override { return _u8L("Paint-on seam editing"); }
|
||||
|
||||
private:
|
||||
|
|
|
@ -401,11 +401,20 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
|||
for (size_t idx : points_idxs)
|
||||
points_inside.push_back(points[idx].cast<float>());
|
||||
|
||||
// Only select/deselect points that are actually visible
|
||||
// Only select/deselect points that are actually visible. We want to check not only
|
||||
// the point itself, but also the center of base of its cone, so the points don't hide
|
||||
// under every miniature irregularity on the model. Remember the actual number and
|
||||
// append the cone bases.
|
||||
size_t orig_pts_num = points_inside.size();
|
||||
for (size_t idx : points_idxs)
|
||||
points_inside.emplace_back((trafo.get_matrix().cast<float>() * (m_editing_cache[idx].support_point.pos + m_editing_cache[idx].normal)).cast<float>());
|
||||
|
||||
for (size_t idx : m_c->raycaster()->raycaster()->get_unobscured_idxs(
|
||||
trafo, wxGetApp().plater()->get_camera(), points_inside,
|
||||
m_c->object_clipper()->get_clipping_plane()))
|
||||
{
|
||||
if (idx >= orig_pts_num) // this is a cone-base, get index of point it belongs to
|
||||
idx -= orig_pts_num;
|
||||
if (rectangle_status == GLSelectionRectangle::Deselect)
|
||||
unselect_point(points_idxs[idx]);
|
||||
else
|
||||
|
|
|
@ -1251,14 +1251,16 @@ bool GLGizmosManager::activate_gizmo(EType type)
|
|||
if (! m_parent.get_gizmos_manager().is_serializing()
|
||||
&& old_gizmo->wants_enter_leave_snapshots())
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(),
|
||||
Slic3r::format(_utf8("Leaving %1%"), old_gizmo->get_name(false)),
|
||||
Slic3r::format(_CTX_utf8("Leaving %1%", "undo/redo action name, placeholder "
|
||||
"expands to a name of a gizmo being closed"), old_gizmo->get_name(false)),
|
||||
UndoRedo::SnapshotType::LeavingGizmoWithAction);
|
||||
}
|
||||
|
||||
if (new_gizmo && ! m_parent.get_gizmos_manager().is_serializing()
|
||||
&& new_gizmo->wants_enter_leave_snapshots())
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(),
|
||||
Slic3r::format(_utf8("Entering %1%"), new_gizmo->get_name(false)),
|
||||
Slic3r::format(_CTX_utf8("Entering %1%", "undo/redo action name, placeholder "
|
||||
"expands to a name of a gizmo being opened"), new_gizmo->get_name(false)),
|
||||
UndoRedo::SnapshotType::EnteringGizmo);
|
||||
|
||||
m_current = type;
|
||||
|
|
|
@ -413,9 +413,9 @@ void HintDatabase::load_hints_from_file(const boost::filesystem::path& path)
|
|||
// open preferences
|
||||
} else if(dict["hypertext_type"] == "preferences") {
|
||||
int page = static_cast<Preset::Type>(std::atoi(dict["hypertext_preferences_page"].c_str()));
|
||||
HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, [page]() { wxGetApp().open_preferences(page); } };
|
||||
std::string item = dict["hypertext_preferences_item"];
|
||||
HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, [page, item]() { wxGetApp().open_preferences(page, item); } };
|
||||
m_loaded_hints.emplace_back(hint_data);
|
||||
|
||||
} else if (dict["hypertext_type"] == "plater") {
|
||||
std::string item = dict["hypertext_plater_item"];
|
||||
HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, true, documentation_link, [item]() { wxGetApp().plater()->canvas3D()->highlight_toolbar_item(item); } };
|
||||
|
|
|
@ -82,10 +82,10 @@ void MeshClipper::recalculate_triangles()
|
|||
const Transform3f& instance_matrix_no_translation_no_scaling = m_trafo.get_matrix(true,false,true).cast<float>();
|
||||
const Vec3f& scaling = m_trafo.get_scaling_factor().cast<float>();
|
||||
// Calculate clipping plane normal in mesh coordinates.
|
||||
Vec3f up_noscale = instance_matrix_no_translation_no_scaling.inverse() * m_plane.get_normal().cast<float>();
|
||||
Vec3d up (up_noscale(0)*scaling(0), up_noscale(1)*scaling(1), up_noscale(2)*scaling(2));
|
||||
const Vec3f up_noscale = instance_matrix_no_translation_no_scaling.inverse() * m_plane.get_normal().cast<float>();
|
||||
const Vec3d up (up_noscale(0)*scaling(0), up_noscale(1)*scaling(1), up_noscale(2)*scaling(2));
|
||||
// Calculate distance from mesh origin to the clipping plane (in mesh coordinates).
|
||||
float height_mesh = m_plane.distance(m_trafo.get_offset()) * (up_noscale.norm()/up.norm());
|
||||
const float height_mesh = m_plane.distance(m_trafo.get_offset()) * (up_noscale.norm()/up.norm());
|
||||
|
||||
// Now do the cutting
|
||||
MeshSlicingParams slicing_params;
|
||||
|
@ -94,7 +94,7 @@ void MeshClipper::recalculate_triangles()
|
|||
ExPolygons expolys = union_ex(slice_mesh(m_mesh->its, height_mesh, slicing_params));
|
||||
|
||||
if (m_negative_mesh && !m_negative_mesh->empty()) {
|
||||
ExPolygons neg_expolys = union_ex(slice_mesh(m_negative_mesh->its, height_mesh, slicing_params));
|
||||
const ExPolygons neg_expolys = union_ex(slice_mesh(m_negative_mesh->its, height_mesh, slicing_params));
|
||||
expolys = diff_ex(expolys, neg_expolys);
|
||||
}
|
||||
|
||||
|
@ -110,13 +110,13 @@ void MeshClipper::recalculate_triangles()
|
|||
// Now remove whatever ended up below the limiting plane (e.g. sinking objects).
|
||||
// First transform the limiting plane from world to mesh coords.
|
||||
// Note that inverse of tr transforms the plane from world to horizontal.
|
||||
Vec3d normal_old = m_limiting_plane.get_normal().normalized();
|
||||
Vec3d normal_new = (tr.matrix().block<3,3>(0,0).transpose() * normal_old).normalized();
|
||||
const Vec3d normal_old = m_limiting_plane.get_normal().normalized();
|
||||
const Vec3d normal_new = (tr.matrix().block<3,3>(0,0).transpose() * normal_old).normalized();
|
||||
|
||||
// normal_new should now be the plane normal in mesh coords. To find the offset,
|
||||
// transform a point and set offset so it belongs to the transformed plane.
|
||||
Vec3d pt = Vec3d::Zero();
|
||||
double plane_offset = m_limiting_plane.get_data()[3];
|
||||
const double plane_offset = m_limiting_plane.get_data()[3];
|
||||
if (std::abs(normal_old.z()) > 0.5) // normal is normalized, at least one of the coords if larger than sqrt(3)/3 = 0.57
|
||||
pt.z() = - plane_offset / normal_old.z();
|
||||
else if (std::abs(normal_old.y()) > 0.5)
|
||||
|
@ -124,27 +124,25 @@ void MeshClipper::recalculate_triangles()
|
|||
else
|
||||
pt.x() = - plane_offset / normal_old.x();
|
||||
pt = tr.inverse() * pt;
|
||||
double offset = -(normal_new.dot(pt));
|
||||
const double offset = -(normal_new.dot(pt));
|
||||
|
||||
if (std::abs(normal_old.dot(m_plane.get_normal().normalized())) > 0.99) {
|
||||
// The cuts are parallel, show all or nothing.
|
||||
if (offset < height_mesh)
|
||||
if (normal_old.dot(m_plane.get_normal().normalized()) < 0.0 && offset < height_mesh)
|
||||
expolys.clear();
|
||||
} else {
|
||||
// The cut is a horizontal plane defined by z=height_mesh.
|
||||
// ax+by+e=0 is the line of intersection with the limiting plane.
|
||||
// Normalized so a^2 + b^2 = 1.
|
||||
double len = std::hypot(normal_new.x(), normal_new.y());
|
||||
const double len = std::hypot(normal_new.x(), normal_new.y());
|
||||
if (len == 0.)
|
||||
return;
|
||||
double a = normal_new.x() / len;
|
||||
double b = normal_new.y() / len;
|
||||
double e = (normal_new.z() * height_mesh + offset) / len;
|
||||
if (b == 0.)
|
||||
return;
|
||||
const double a = normal_new.x() / len;
|
||||
const double b = normal_new.y() / len;
|
||||
const double e = (normal_new.z() * height_mesh + offset) / len;
|
||||
|
||||
// We need a half-plane to limit the cut. Get angle of the intersecting line.
|
||||
double angle = std::atan(-a/b);
|
||||
double angle = (b != 0.0) ? std::atan(-a / b) : ((a < 0.0) ? -0.5 * M_PI : 0.5 * M_PI);
|
||||
if (b > 0) // select correct half-plane
|
||||
angle += M_PI;
|
||||
|
||||
|
@ -152,7 +150,7 @@ void MeshClipper::recalculate_triangles()
|
|||
// it so it lies on our line. This will be the figure to subtract
|
||||
// from the cut. The coordinates must not overflow after the transform,
|
||||
// make the rectangle a bit smaller.
|
||||
coord_t size = (std::numeric_limits<coord_t>::max() - scale_(std::max(std::abs(e*a), std::abs(e*b)))) / 4;
|
||||
const coord_t size = (std::numeric_limits<coord_t>::max() - scale_(std::max(std::abs(e*a), std::abs(e*b)))) / 4;
|
||||
Polygons ep {Polygon({Point(-size, 0), Point(size, 0), Point(size, 2*size), Point(-size, 2*size)})};
|
||||
ep.front().rotate(angle);
|
||||
ep.front().translate(scale_(-e * a), scale_(-e * b));
|
||||
|
@ -169,7 +167,7 @@ void MeshClipper::recalculate_triangles()
|
|||
m_vertex_array.push_geometry(tr * Vec3d((*(it+0))(0), (*(it+0))(1), height_mesh), up);
|
||||
m_vertex_array.push_geometry(tr * Vec3d((*(it+1))(0), (*(it+1))(1), height_mesh), up);
|
||||
m_vertex_array.push_geometry(tr * Vec3d((*(it+2))(0), (*(it+2))(1), height_mesh), up);
|
||||
size_t idx = it - m_triangles2d.cbegin();
|
||||
const size_t idx = it - m_triangles2d.cbegin();
|
||||
m_vertex_array.push_triangle(idx, idx+1, idx+2);
|
||||
}
|
||||
m_vertex_array.finalize_geometry(true);
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <wx/statbmp.h>
|
||||
#include <wx/scrolwin.h>
|
||||
#include <wx/clipbrd.h>
|
||||
#include <wx/checkbox.h>
|
||||
#include <wx/html/htmlwin.h>
|
||||
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
|
@ -75,6 +76,26 @@ void MsgDialog::add_btn(wxWindowID btn_id, bool set_focus /*= false*/)
|
|||
btn->Bind(wxEVT_BUTTON, [this, btn_id](wxCommandEvent&) { this->EndModal(btn_id); });
|
||||
};
|
||||
|
||||
void MsgDialog::apply_style(long style)
|
||||
{
|
||||
if (style & wxOK) add_btn(wxID_OK, true);
|
||||
if (style & wxYES) add_btn(wxID_YES);
|
||||
if (style & wxNO) add_btn(wxID_NO);
|
||||
if (style & wxCANCEL) add_btn(wxID_CANCEL);
|
||||
|
||||
logo->SetBitmap(create_scaled_bitmap(style & wxICON_WARNING ? "exclamation" :
|
||||
style & wxICON_INFORMATION ? "info" :
|
||||
style & wxICON_QUESTION ? "question" : "PrusaSlicer"/*"_192px_grayscale.png"*/, this, 84));
|
||||
}
|
||||
|
||||
void MsgDialog::finalize()
|
||||
{
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
Fit();
|
||||
this->CenterOnParent();
|
||||
}
|
||||
|
||||
|
||||
// Text shown as HTML, so that mouse selection and Ctrl-V to copy will work.
|
||||
static void add_msg_content(wxWindow* parent, wxBoxSizer* content_sizer, wxString msg, bool monospaced_font = false)
|
||||
{
|
||||
|
@ -156,11 +177,9 @@ ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg, bool monospaced_
|
|||
// Use a small bitmap with monospaced font, as the error text will not be wrapped.
|
||||
logo->SetBitmap(create_scaled_bitmap("PrusaSlicer_192px_grayscale.png", this, monospaced_font ? 48 : /*1*/84));
|
||||
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
|
||||
SetMaxSize(wxSize(-1, CONTENT_MAX_HEIGHT*wxGetApp().em_unit()));
|
||||
Fit();
|
||||
this->CenterOnParent();
|
||||
|
||||
finalize();
|
||||
}
|
||||
|
||||
// WarningDialog
|
||||
|
@ -173,16 +192,8 @@ WarningDialog::WarningDialog(wxWindow *parent,
|
|||
wxString::Format(_L("%s has a warning")+":", SLIC3R_APP_NAME), wxID_NONE)
|
||||
{
|
||||
add_msg_content(this, content_sizer, message);
|
||||
|
||||
if (style & wxOK) add_btn(wxID_OK, true);
|
||||
if (style & wxYES) add_btn(wxID_YES);
|
||||
if (style & wxNO) add_btn(wxID_NO);
|
||||
|
||||
logo->SetBitmap(create_scaled_bitmap("PrusaSlicer_192px_grayscale.png", this, 84));
|
||||
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
Fit();
|
||||
this->CenterOnParent();
|
||||
apply_style(style);
|
||||
finalize();
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
@ -195,23 +206,37 @@ MessageDialog::MessageDialog(wxWindow* parent,
|
|||
: MsgDialog(parent, caption.IsEmpty() ? wxString::Format(_L("%s info"), SLIC3R_APP_NAME) : caption, wxEmptyString, wxID_NONE)
|
||||
{
|
||||
add_msg_content(this, content_sizer, message);
|
||||
|
||||
if (style & wxOK) add_btn(wxID_OK, true);
|
||||
if (style & wxYES) add_btn(wxID_YES);
|
||||
if (style & wxNO) add_btn(wxID_NO);
|
||||
if (style & wxCANCEL) add_btn(wxID_CANCEL);
|
||||
|
||||
logo->SetBitmap(create_scaled_bitmap(style & wxICON_WARNING ? "exclamation" :
|
||||
style & wxICON_INFORMATION ? "info" :
|
||||
style & wxICON_QUESTION ? "question" : "PrusaSlicer_192px_grayscale.png", this, 84));
|
||||
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
Fit();
|
||||
this->CenterOnParent();
|
||||
apply_style(style);
|
||||
finalize();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// MessageWithCheckDialog
|
||||
|
||||
MessageWithCheckDialog::MessageWithCheckDialog( wxWindow* parent,
|
||||
const wxString& message,
|
||||
const wxString& checkbox_label,
|
||||
const wxString& caption/* = wxEmptyString*/,
|
||||
long style/* = wxOK*/)
|
||||
: MsgDialog(parent, caption.IsEmpty() ? wxString::Format(_L("%s info"), SLIC3R_APP_NAME) : caption, wxEmptyString, wxID_NONE)
|
||||
{
|
||||
add_msg_content(this, content_sizer, message);
|
||||
|
||||
m_check = new wxCheckBox(this, wxID_ANY, checkbox_label);
|
||||
content_sizer->Add(m_check, 0, wxTOP, 10);
|
||||
|
||||
apply_style(style);
|
||||
finalize();
|
||||
}
|
||||
|
||||
bool MessageWithCheckDialog::GetCheckVal()
|
||||
{
|
||||
if (m_check)
|
||||
return m_check->GetValue();
|
||||
return false;
|
||||
}
|
||||
|
||||
// InfoDialog
|
||||
|
||||
InfoDialog::InfoDialog(wxWindow* parent, const wxString &title, const wxString& msg)
|
||||
|
@ -222,9 +247,7 @@ InfoDialog::InfoDialog(wxWindow* parent, const wxString &title, const wxString&
|
|||
// Set info bitmap
|
||||
logo->SetBitmap(create_scaled_bitmap("info", this, 84));
|
||||
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
|
||||
Fit();
|
||||
finalize();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -43,6 +43,8 @@ protected:
|
|||
MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxWindowID button_id = wxID_OK, wxBitmap bitmap = wxNullBitmap);
|
||||
|
||||
void add_btn(wxWindowID btn_id, bool set_focus = false);
|
||||
void apply_style(long style);
|
||||
void finalize();
|
||||
|
||||
wxFont boldfont;
|
||||
wxBoxSizer *content_sizer;
|
||||
|
@ -113,6 +115,23 @@ public:
|
|||
};
|
||||
#endif
|
||||
|
||||
class MessageWithCheckDialog : public MsgDialog
|
||||
{
|
||||
wxCheckBox* m_check{ nullptr };
|
||||
public:
|
||||
MessageWithCheckDialog(wxWindow* parent,
|
||||
const wxString& message,
|
||||
const wxString& checkbox_label,
|
||||
const wxString& caption = wxEmptyString,
|
||||
long style = wxOK);
|
||||
MessageWithCheckDialog(MessageWithCheckDialog&&) = delete;
|
||||
MessageWithCheckDialog(const MessageWithCheckDialog&) = delete;
|
||||
MessageWithCheckDialog& operator=(MessageWithCheckDialog&&) = delete;
|
||||
MessageWithCheckDialog& operator=(const MessageWithCheckDialog&) = delete;
|
||||
virtual ~MessageWithCheckDialog() = default;
|
||||
|
||||
bool GetCheckVal();
|
||||
};
|
||||
|
||||
// Generic info dialog, used for displaying exceptions
|
||||
class InfoDialog : public MsgDialog
|
||||
|
|
|
@ -2068,9 +2068,11 @@ bool NotificationManager::update_notifications(GLCanvas3D& canvas)
|
|||
if ((*it).remaining_time > 0)
|
||||
(*it).remaining_time -= time_since_render;
|
||||
if ((*it).remaining_time <= 0) {
|
||||
if ((*it).condition_callback()) { // push notification, erase it from waiting list (frame is scheduled by push)
|
||||
if ((*it).notification && (*it).condition_callback()) { // push notification, erase it from waiting list (frame is scheduled by push)
|
||||
(*it).notification->reset_timer();
|
||||
if (push_notification_data(std::move((*it).notification), 0)) {
|
||||
// if activate_existing returns false, we expect push to return true.
|
||||
if(!this->activate_existing((*it).notification.get()) || (*it).delay_interval == 0) {
|
||||
push_notification_data(std::move((*it).notification), 0);
|
||||
it = m_waiting_notifications.erase(it);
|
||||
continue;
|
||||
}
|
||||
|
@ -2107,11 +2109,13 @@ bool NotificationManager::activate_existing(const NotificationManager::PopNotifi
|
|||
const std::string &new_text = notification->get_data().text1;
|
||||
for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end(); ++it) {
|
||||
if ((*it)->get_type() == new_type && !(*it)->is_finished()) {
|
||||
// multiple of one type allowed, but must have different text
|
||||
if (std::find(m_multiple_types.begin(), m_multiple_types.end(), new_type) != m_multiple_types.end()) {
|
||||
// If found same type and same text, return true - update will be performed on the old notif
|
||||
if ((*it)->compare_text(new_text) == false) {
|
||||
continue;
|
||||
}
|
||||
// multiple of one type allowed, but must have different text nad ObjectID
|
||||
} else if (new_type == NotificationType::SlicingWarning) {
|
||||
auto w1 = dynamic_cast<const ObjectIDNotification*>(notification);
|
||||
auto w2 = dynamic_cast<const ObjectIDNotification*>(it->get());
|
||||
|
|
|
@ -112,7 +112,7 @@ enum class NotificationType
|
|||
// information about netfabb is finished repairing model (blocking proccess)
|
||||
NetfabbFinished,
|
||||
// Short meesage to fill space between start and finish of export
|
||||
ExportOngoing
|
||||
ExportOngoing,
|
||||
};
|
||||
|
||||
class NotificationManager
|
||||
|
@ -706,6 +706,7 @@ private:
|
|||
// Otherwise another delay interval waiting. Timestamp is 0.
|
||||
// Note that notification object is constructed when being added to the waiting list, but there are no updates called on it and its timer is reset at regular push.
|
||||
// Also note that no control of same notification is done during push_delayed_notification_data but if waiting notif fails to push, it continues waiting.
|
||||
// If delay_interval is 0, notification is pushed only after initial_delay no matter the result.
|
||||
void push_delayed_notification_data(std::unique_ptr<NotificationManager::PopNotification> notification, std::function<bool(void)> condition_callback, int64_t initial_delay, int64_t delay_interval);
|
||||
//finds older notification of same type and moves it to the end of queue. returns true if found
|
||||
bool activate_existing(const NotificationManager::PopNotification* notification);
|
||||
|
|
|
@ -2312,6 +2312,9 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
|||
auto *new_model = (!load_model || one_by_one) ? nullptr : new Slic3r::Model();
|
||||
std::vector<size_t> obj_idxs;
|
||||
|
||||
int answer_convert_from_meters = wxOK_DEFAULT;
|
||||
int answer_convert_from_imperial_units = wxOK_DEFAULT;
|
||||
|
||||
for (size_t i = 0; i < input_files.size(); ++i) {
|
||||
#ifdef _WIN32
|
||||
auto path = input_files[i];
|
||||
|
@ -2469,26 +2472,48 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
|||
// Convert even if the object is big.
|
||||
convert_from_imperial_units(model, false);
|
||||
else if (model.looks_like_saved_in_meters()) {
|
||||
MessageDialog msg_dlg(q, format_wxstr(_L_PLURAL(
|
||||
"The dimensions of the object from file %s seem to be defined in meters.\n"
|
||||
"The internal unit of PrusaSlicer are millimeters. Do you want to recalculate the dimensions of the object?",
|
||||
"The dimensions of some objects from file %s seem to be defined in meters.\n"
|
||||
"The internal unit of PrusaSlicer are millimeters. Do you want to recalculate the dimensions of these objects?", model.objects.size()), from_path(filename)) + "\n",
|
||||
_L("The object is too small"), wxICON_WARNING | wxYES | wxNO);
|
||||
if (msg_dlg.ShowModal() == wxID_YES)
|
||||
//FIXME up-scale only the small parts?
|
||||
model.convert_from_meters(true);
|
||||
auto convert_model_if = [](Model& model, bool condition) {
|
||||
if (condition)
|
||||
//FIXME up-scale only the small parts?
|
||||
model.convert_from_meters(true);
|
||||
};
|
||||
if (answer_convert_from_meters == wxOK_DEFAULT) {
|
||||
MessageWithCheckDialog dlg(q, format_wxstr(_L_PLURAL(
|
||||
"The dimensions of the object from file %s seem to be defined in meters.\n"
|
||||
"The internal unit of PrusaSlicer are millimeters. Do you want to recalculate the dimensions of the object?",
|
||||
"The dimensions of some objects from file %s seem to be defined in meters.\n"
|
||||
"The internal unit of PrusaSlicer are millimeters. Do you want to recalculate the dimensions of these objects?", model.objects.size()), from_path(filename)) + "\n",
|
||||
_L("Apply to all the remaining small objects being loaded."),
|
||||
_L("The object is too small"), wxICON_WARNING | wxYES | wxNO);
|
||||
int answer = dlg.ShowModal();
|
||||
if (dlg.GetCheckVal())
|
||||
answer_convert_from_meters = answer;
|
||||
else
|
||||
convert_model_if(model, answer == wxID_YES);
|
||||
}
|
||||
convert_model_if(model, answer_convert_from_meters == wxID_YES);
|
||||
}
|
||||
else if (model.looks_like_imperial_units()) {
|
||||
MessageDialog msg_dlg(q, format_wxstr(_L_PLURAL(
|
||||
"The dimensions of the object from file %s seem to be defined in inches.\n"
|
||||
"The internal unit of PrusaSlicer are millimeters. Do you want to recalculate the dimensions of the object?",
|
||||
"The dimensions of some objects from file %s seem to be defined in inches.\n"
|
||||
"The internal unit of PrusaSlicer are millimeters. Do you want to recalculate the dimensions of these objects?", model.objects.size()), from_path(filename)) + "\n",
|
||||
_L("The object is too small"), wxICON_WARNING | wxYES | wxNO);
|
||||
if (msg_dlg.ShowModal() == wxID_YES)
|
||||
//FIXME up-scale only the small parts?
|
||||
convert_from_imperial_units(model, true);
|
||||
auto convert_model_if = [convert_from_imperial_units](Model& model, bool condition) {
|
||||
if (condition)
|
||||
//FIXME up-scale only the small parts?
|
||||
convert_from_imperial_units(model, true);
|
||||
};
|
||||
if (answer_convert_from_imperial_units == wxOK_DEFAULT) {
|
||||
MessageWithCheckDialog dlg(q, format_wxstr(_L_PLURAL(
|
||||
"The dimensions of the object from file %s seem to be defined in inches.\n"
|
||||
"The internal unit of PrusaSlicer are millimeters. Do you want to recalculate the dimensions of the object?",
|
||||
"The dimensions of some objects from file %s seem to be defined in inches.\n"
|
||||
"The internal unit of PrusaSlicer are millimeters. Do you want to recalculate the dimensions of these objects?", model.objects.size()), from_path(filename)) + "\n",
|
||||
_L("Apply to all the remaining small objects being loaded."),
|
||||
_L("The object is too small"), wxICON_WARNING | wxYES | wxNO);
|
||||
int answer = dlg.ShowModal();
|
||||
if (dlg.GetCheckVal())
|
||||
answer_convert_from_imperial_units = answer;
|
||||
else
|
||||
convert_model_if(model, answer == wxID_YES);
|
||||
}
|
||||
convert_model_if(model, answer_convert_from_imperial_units == wxID_YES);
|
||||
}
|
||||
|
||||
if (model.looks_like_multipart_object()) {
|
||||
|
@ -3269,6 +3294,7 @@ void Plater::priv::export_gcode(fs::path output_path, bool output_path_on_remova
|
|||
show_warning_dialog = true;
|
||||
if (! output_path.empty()) {
|
||||
background_process.schedule_export(output_path.string(), output_path_on_removable_media);
|
||||
notification_manager->push_delayed_notification(NotificationType::ExportOngoing, []() {return true; }, 1000, 0);
|
||||
} else {
|
||||
background_process.schedule_upload(std::move(upload_job));
|
||||
}
|
||||
|
@ -4005,7 +4031,6 @@ void Plater::priv::on_export_began(wxCommandEvent& evt)
|
|||
{
|
||||
if (show_warning_dialog)
|
||||
warnings_dialog();
|
||||
notification_manager->push_delayed_notification(NotificationType::ExportOngoing, [](){return true;}, 1000, 1000);
|
||||
}
|
||||
void Plater::priv::on_slicing_began()
|
||||
{
|
||||
|
@ -6835,6 +6860,8 @@ bool Plater::is_render_statistic_dialog_visible() const
|
|||
|
||||
Plater::TakeSnapshot::TakeSnapshot(Plater *plater, const std::string &snapshot_name)
|
||||
: TakeSnapshot(plater, from_u8(snapshot_name)) {}
|
||||
Plater::TakeSnapshot::TakeSnapshot(Plater* plater, const std::string& snapshot_name, UndoRedo::SnapshotType snapshot_type)
|
||||
: TakeSnapshot(plater, from_u8(snapshot_name), snapshot_type) {}
|
||||
|
||||
|
||||
// Wrapper around wxWindow::PopupMenu to suppress error messages popping out while tracking the popup menu.
|
||||
|
|
|
@ -394,6 +394,7 @@ public:
|
|||
m_plater->take_snapshot(snapshot_name);
|
||||
m_plater->suppress_snapshots();
|
||||
}
|
||||
TakeSnapshot(Plater* plater, const std::string& snapshot_name, UndoRedo::SnapshotType snapshot_type);
|
||||
TakeSnapshot(Plater *plater, const wxString &snapshot_name, UndoRedo::SnapshotType snapshot_type) : m_plater(plater)
|
||||
{
|
||||
m_plater->take_snapshot(snapshot_name, snapshot_type);
|
||||
|
|
|
@ -1786,7 +1786,10 @@ void Selection::set_caches()
|
|||
void Selection::do_add_volume(unsigned int volume_idx)
|
||||
{
|
||||
m_list.insert(volume_idx);
|
||||
(*m_volumes)[volume_idx]->selected = true;
|
||||
GLVolume* v = (*m_volumes)[volume_idx];
|
||||
v->selected = true;
|
||||
if (v->hover == GLVolume::HS_Select || v->hover == GLVolume::HS_Deselect)
|
||||
v->hover = GLVolume::HS_Hover;
|
||||
}
|
||||
|
||||
void Selection::do_add_volumes(const std::vector<unsigned int>& volume_idxs)
|
||||
|
|
|
@ -35,6 +35,8 @@
|
|||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <netlistmgr.h>
|
||||
#include <atlbase.h>
|
||||
#include <Iphlpapi.h>
|
||||
#pragma comment(lib, "iphlpapi.lib")
|
||||
#elif __APPLE__
|
||||
|
@ -132,6 +134,28 @@ public:
|
|||
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
static bool check_internet_connection_win()
|
||||
{
|
||||
bool internet = true; // return true if COM object creation fails.
|
||||
|
||||
if (CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) == S_OK) {
|
||||
{
|
||||
CComPtr<INetworkListManager> pNLM;
|
||||
if (pNLM.CoCreateInstance(CLSID_NetworkListManager) == S_OK) {
|
||||
NLM_CONNECTIVITY status;
|
||||
pNLM->GetConnectivity(&status);
|
||||
internet = (status & (NLM_CONNECTIVITY_IPV4_INTERNET | NLM_CONNECTIVITY_IPV6_INTERNET));
|
||||
}
|
||||
}
|
||||
CoUninitialize();
|
||||
}
|
||||
|
||||
return internet;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// Last version where the info was sent / dialog dismissed is saved in appconfig.
|
||||
// Only show the dialog when this info is not found (e.g. fresh install) or when
|
||||
// current version is newer. Only major and minor versions are compared.
|
||||
|
@ -157,16 +181,19 @@ static bool should_dialog_be_shown()
|
|||
if (! new_version)
|
||||
return false;
|
||||
|
||||
// We'll misuse the version check to check internet connection here.
|
||||
bool is_internet = false;
|
||||
Http::get(wxGetApp().app_config->version_check_url())
|
||||
.size_limit(SLIC3R_VERSION_BODY_MAX)
|
||||
.timeout_max(2)
|
||||
.on_complete([&](std::string, unsigned) {
|
||||
is_internet = true;
|
||||
})
|
||||
.perform_sync();
|
||||
return is_internet;
|
||||
// We might want to check that the internet connection is ready so we don't open the dialog
|
||||
// if it cannot really send any data. Using a dummy HTTP GET request led to
|
||||
// https://forum.prusaprinters.org/forum/prusaslicer/prusaslicer-2-4-0-beta1-is-out/#post-518488.
|
||||
// It might also trigger security softwares, which would look bad and would lead to questions
|
||||
// about what PS is doing. We better use some less intrusive way of checking the connection.
|
||||
|
||||
// As of now, this is only implemented on Win. The other platforms do not check beforehand.
|
||||
|
||||
#ifdef _WIN32
|
||||
return check_internet_connection_win();
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1740,11 +1740,14 @@ void TabPrint::update()
|
|||
// Note: This workaround works till "support_material" and "overhangs" is exclusive sets of mutually no-exclusive parameters.
|
||||
// But it should be corrected when we will have more such sets.
|
||||
// Disable check of the compatibility of the "support_material" and "overhangs" options for saved user profile
|
||||
const Preset& selected_preset = m_preset_bundle->prints.get_selected_preset();
|
||||
bool is_user_and_saved_preset = !selected_preset.is_system && !selected_preset.is_dirty;
|
||||
bool support_material_overhangs_queried = m_config->opt_bool("support_material") && !m_config->opt_bool("overhangs");
|
||||
if (!m_config_manipulation.is_initialized_support_material_overhangs_queried()) {
|
||||
const Preset& selected_preset = m_preset_bundle->prints.get_selected_preset();
|
||||
bool is_user_and_saved_preset = !selected_preset.is_system && !selected_preset.is_dirty;
|
||||
bool support_material_overhangs_queried = m_config->opt_bool("support_material") && !m_config->opt_bool("overhangs");
|
||||
m_config_manipulation.initialize_support_material_overhangs_queried(is_user_and_saved_preset && support_material_overhangs_queried);
|
||||
}
|
||||
|
||||
m_config_manipulation.update_print_fff_config(m_config, true, is_user_and_saved_preset && support_material_overhangs_queried);
|
||||
m_config_manipulation.update_print_fff_config(m_config, true);
|
||||
|
||||
update_description_lines();
|
||||
Layout();
|
||||
|
|
|
@ -107,7 +107,7 @@ bool Repetier::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Error
|
|||
|
||||
bool res = true;
|
||||
|
||||
auto url = make_url((boost::format("printer/model/%1%") % port).str());
|
||||
auto url = upload_data.start_print?make_url((boost::format("printer/job/%1%") % port).str()):make_url((boost::format("printer/model/%1%") % port).str());
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3%, filename: %4%, path: %5%, print: %6%, group: %7%")
|
||||
% name
|
||||
|
@ -125,6 +125,10 @@ bool Repetier::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Error
|
|||
http.form_add("group", upload_data.group);
|
||||
}
|
||||
|
||||
if(upload_data.start_print) {
|
||||
http.form_add("name", upload_filename.string());
|
||||
}
|
||||
|
||||
http.form_add("a", "upload")
|
||||
.form_add_file("filename", upload_data.source_path.string(), upload_filename.string())
|
||||
.on_complete([&](std::string body, unsigned status) {
|
||||
|
|
|
@ -27,7 +27,7 @@ public:
|
|||
bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const override;
|
||||
bool has_auto_discovery() const override { return false; }
|
||||
bool can_test() const override { return true; }
|
||||
bool can_start_print() const override { return false; }
|
||||
bool can_start_print() const override { return true; }
|
||||
bool supports_multiple_printers() const override { return true; }
|
||||
std::string get_host() const override { return host; }
|
||||
|
||||
|
|
|
@ -1078,7 +1078,7 @@ std::vector<Snapshot>::iterator StackImpl::release_snapshots(std::vector<Snapsho
|
|||
{
|
||||
assert(! m_snapshots.empty());
|
||||
assert(begin <= end);
|
||||
if (m_saved_snapshot_time >= begin->timestamp && (end == m_snapshots.end() || m_saved_snapshot_time < end->timestamp)) {
|
||||
if (m_saved_snapshot_time != size_t(-1) && m_saved_snapshot_time >= begin->timestamp && (end == m_snapshots.end() || m_saved_snapshot_time < end->timestamp)) {
|
||||
assert(m_saved_snapshot_time <= m_snapshots.back().timestamp);
|
||||
auto it_saved = std::lower_bound(begin, end, Snapshot(m_saved_snapshot_time));
|
||||
assert(it_saved != m_snapshots.end() && it_saved->timestamp == m_saved_snapshot_time);
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "libslic3r/Polyline.hpp"
|
||||
#include "libslic3r/Line.hpp"
|
||||
#include "libslic3r/Geometry.hpp"
|
||||
#include "libslic3r/Geometry/Circle.hpp"
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
#include "libslic3r/ShortestPath.hpp"
|
||||
|
||||
|
@ -320,6 +321,24 @@ SCENARIO("Circle Fit, TaubinFit with Newton's method", "[Geometry]") {
|
|||
}
|
||||
}
|
||||
|
||||
TEST_CASE("smallest_enclosing_circle_welzl", "[Geometry]") {
|
||||
// Some random points in plane.
|
||||
Points pts {
|
||||
{ 89243, 4359 }, { 763465, 59687 }, { 3245, 734987 }, { 2459867, 987634 }, { 759866, 67843982 }, { 9754687, 9834658 }, { 87235089, 743984373 },
|
||||
{ 65874456, 2987546 }, { 98234524, 657654873 }, { 786243598, 287934765 }, { 824356, 734265 }, { 82576449, 7864534 }, { 7826345, 3984765 }
|
||||
};
|
||||
|
||||
const auto c = Slic3r::Geometry::smallest_enclosing_circle_welzl(pts);
|
||||
// The radius returned is inflated by SCALED_EPSILON, thus all points should be inside.
|
||||
bool all_inside = std::all_of(pts.begin(), pts.end(), [c](const Point &pt){ return c.contains(pt.cast<double>()); });
|
||||
auto c2(c);
|
||||
c2.radius -= SCALED_EPSILON * 2.1;
|
||||
auto num_on_boundary = std::count_if(pts.begin(), pts.end(), [c2](const Point& pt) { return ! c2.contains(pt.cast<double>(), SCALED_EPSILON); });
|
||||
|
||||
REQUIRE(all_inside);
|
||||
REQUIRE(num_on_boundary == 3);
|
||||
}
|
||||
|
||||
SCENARIO("Path chaining", "[Geometry]") {
|
||||
GIVEN("A path") {
|
||||
std::vector<Point> points = { Point(26,26),Point(52,26),Point(0,26),Point(26,52),Point(26,0),Point(0,52),Point(52,52),Point(52,0) };
|
||||
|
|
|
@ -51,6 +51,20 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") {
|
|||
SECTION("math: max(13.4, -1238.1)") { REQUIRE(std::stod(parser.process("{max(13.4, -1238.1)}")) == Approx(13.4)); }
|
||||
SECTION("math: int(13.4)") { REQUIRE(parser.process("{int(13.4)}") == "13"); }
|
||||
SECTION("math: int(-13.4)") { REQUIRE(parser.process("{int(-13.4)}") == "-13"); }
|
||||
SECTION("math: round(13.4)") { REQUIRE(parser.process("{round(13.4)}") == "13"); }
|
||||
SECTION("math: round(-13.4)") { REQUIRE(parser.process("{round(-13.4)}") == "-13"); }
|
||||
SECTION("math: round(13.6)") { REQUIRE(parser.process("{round(13.6)}") == "14"); }
|
||||
SECTION("math: round(-13.6)") { REQUIRE(parser.process("{round(-13.6)}") == "-14"); }
|
||||
SECTION("math: digits(5, 15)") { REQUIRE(parser.process("{digits(5, 15)}") == " 5"); }
|
||||
SECTION("math: digits(5., 15)") { REQUIRE(parser.process("{digits(5., 15)}") == " 5"); }
|
||||
SECTION("math: zdigits(5, 15)") { REQUIRE(parser.process("{zdigits(5, 15)}") == "000000000000005"); }
|
||||
SECTION("math: zdigits(5., 15)") { REQUIRE(parser.process("{zdigits(5., 15)}") == "000000000000005"); }
|
||||
SECTION("math: digits(5, 15, 8)") { REQUIRE(parser.process("{digits(5, 15, 8)}") == " 5.00000000"); }
|
||||
SECTION("math: digits(5., 15, 8)") { REQUIRE(parser.process("{digits(5, 15, 8)}") == " 5.00000000"); }
|
||||
SECTION("math: zdigits(5, 15, 8)") { REQUIRE(parser.process("{zdigits(5, 15, 8)}") == "000005.00000000"); }
|
||||
SECTION("math: zdigits(5., 15, 8)") { REQUIRE(parser.process("{zdigits(5, 15, 8)}") == "000005.00000000"); }
|
||||
SECTION("math: digits(13.84375892476, 15, 8)") { REQUIRE(parser.process("{digits(13.84375892476, 15, 8)}") == " 13.84375892"); }
|
||||
SECTION("math: zdigits(13.84375892476, 15, 8)") { REQUIRE(parser.process("{zdigits(13.84375892476, 15, 8)}") == "000013.84375892"); }
|
||||
|
||||
// Test the "coFloatOrPercent" and "xxx_extrusion_width" substitutions.
|
||||
// first_layer_extrusion_width ratio_over first_layer_heigth.
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
#include <libslic3r/EdgeGrid.hpp>
|
||||
#include <libslic3r/Geometry.hpp>
|
||||
|
||||
#include <libslic3r/VoronoiOffset.hpp>
|
||||
#include <libslic3r/VoronoiVisualUtils.hpp>
|
||||
#include <libslic3r/Geometry/VoronoiOffset.hpp>
|
||||
#include <libslic3r/Geometry/VoronoiVisualUtils.hpp>
|
||||
|
||||
#include <numeric>
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue