mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-11-01 05:01:10 -06:00
Merge remote-tracking branch 'remote/master' into SoftFever
# Conflicts: # src/libslic3r/PerimeterGenerator.cpp
This commit is contained in:
commit
769bc14a8a
219 changed files with 11441 additions and 1527 deletions
|
|
@ -143,7 +143,7 @@ int CLI::run(int argc, char **argv)
|
|||
return 1;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "finished setup params, argc="<< argc << std::endl;
|
||||
std::string temp_path = wxStandardPaths::Get().GetTempDir().utf8_str().data();
|
||||
std::string temp_path = wxFileName::GetTempDir().utf8_str().data();
|
||||
set_temporary_dir(temp_path);
|
||||
|
||||
m_extra_config.apply(m_config, true);
|
||||
|
|
|
|||
|
|
@ -658,6 +658,7 @@ namespace ImGui
|
|||
IMGUI_API void EndMainMenuBar(); // only call EndMainMenuBar() if BeginMainMenuBar() returns true!
|
||||
IMGUI_API bool BeginMenu(const char* label, bool enabled = true); // create a sub-menu entry. only call EndMenu() if this returns true!
|
||||
IMGUI_API void EndMenu(); // only call EndMenu() if BeginMenu() returns true!
|
||||
IMGUI_API bool BBLMenuItem(const char* label, const char* shortcut = NULL, bool selected = false, bool enabled = true); // return true when activated.
|
||||
IMGUI_API bool MenuItem(const char* label, const char* shortcut = NULL, bool selected = false, bool enabled = true); // return true when activated.
|
||||
IMGUI_API bool MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled = true); // return true when activated + toggle (*p_selected) if p_selected != NULL
|
||||
|
||||
|
|
|
|||
|
|
@ -283,7 +283,15 @@ void ImGui::TextAlignCenter(const char *label)
|
|||
float item_width = ImGui::CalcItemWidth();
|
||||
float font_size = ImGui::GetFontSize() * strlen(label) / 2;
|
||||
ImGui::SameLine(ImGui::GetCursorPos().x + (item_width - font_size) / 2);
|
||||
ImGui::Text(label);
|
||||
|
||||
if ('X' == *label)
|
||||
ImGui::TextColored(ImVec4(1.0, 0.0, 0.0, 1.0),label);
|
||||
else if ('Y' == *label)
|
||||
ImGui::TextColored(ImVec4(0.0, 0.6, 0.2, 1.0),label);
|
||||
else if ('Z' == *label)
|
||||
ImGui::TextColored(ImVec4(0.0, 0.0, 1.0, 1.0),label);
|
||||
else
|
||||
ImGui::Text(label);
|
||||
}
|
||||
|
||||
void ImGui::TextV(const char* fmt, va_list args)
|
||||
|
|
@ -2070,7 +2078,7 @@ bool ImGui::BBLBeginCombo(const char *label, const char *preview_value, ImGuiCom
|
|||
const ImVec2 label_size = CalcTextSize(label, NULL, true);
|
||||
const float expected_w = CalcItemWidth();
|
||||
const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : expected_w;
|
||||
const ImRect frame_bb(window->DC.CursorPos - ImVec2(0.0, style.FramePadding.y), window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y));
|
||||
const ImRect frame_bb(window->DC.CursorPos - ImVec2(0.0, style.FramePadding.y), window->DC.CursorPos + ImVec2(w - arrow_size * 2, label_size.y + style.FramePadding.y));
|
||||
const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
|
||||
ItemSize(total_bb, style.FramePadding.y);
|
||||
if (!ItemAdd(total_bb, id, &frame_bb)) return false;
|
||||
|
|
@ -2128,7 +2136,7 @@ bool ImGui::BBLBeginCombo(const char *label, const char *preview_value, ImGuiCom
|
|||
popup_max_height_in_items = 4;
|
||||
else if (flags & ImGuiComboFlags_HeightLarge)
|
||||
popup_max_height_in_items = 20;
|
||||
SetNextWindowSizeConstraints(ImVec2(w, 0.0f), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
|
||||
SetNextWindowSizeConstraints(ImVec2(w - arrow_size * 2, 0.0f), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
|
||||
}
|
||||
|
||||
char name[16];
|
||||
|
|
@ -7096,7 +7104,11 @@ bool ImGui::BBLSelectable(const char *label, bool selected, ImGuiSelectableFlags
|
|||
bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
|
||||
if (hovered || g.ActiveId == id) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Border, GetColorU32(ImGuiCol_BorderActive));
|
||||
RenderFrameBorder(ImVec2(bb.Min.x + style.ItemSpacing.x,bb.Min.y), ImVec2(bb.Max.x - style.ItemSpacing.x,bb.Max.y), style.FrameRounding);
|
||||
if(arrow_size == 0) {
|
||||
RenderFrameBorder(bb.Min, ImVec2(bb.Max.x - style.WindowPadding.x, bb.Max.y), style.FrameRounding);
|
||||
} else {
|
||||
RenderFrameBorder(ImVec2(bb.Min.x + style.WindowPadding.x,bb.Min.y), ImVec2(bb.Max.x - style.WindowPadding.x,bb.Max.y), style.FrameRounding);
|
||||
}
|
||||
ImGui::PopStyleColor(1);
|
||||
}
|
||||
// Update NavId when clicking or when Hovering (this doesn't happen on most widgets), so navigation can be resumed with gamepad/keyboard
|
||||
|
|
@ -7118,7 +7130,11 @@ bool ImGui::BBLSelectable(const char *label, bool selected, ImGuiSelectableFlags
|
|||
if (held && (flags & ImGuiSelectableFlags_DrawHoveredWhenHeld)) hovered = true;
|
||||
if (hovered || selected) {
|
||||
const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
|
||||
RenderFrame(ImVec2(bb.Min.x + style.ItemSpacing.x, bb.Min.y), ImVec2(bb.Max.x - style.ItemSpacing.x, bb.Max.y), col, false, 0.0f);
|
||||
if(arrow_size == 0) {
|
||||
RenderFrame(bb.Min, ImVec2(bb.Max.x - style.WindowPadding.x, bb.Max.y), col, false, 0.0f);
|
||||
} else {
|
||||
RenderFrame(ImVec2(bb.Min.x + style.WindowPadding.x, bb.Min.y), ImVec2(bb.Max.x - style.WindowPadding.x, bb.Max.y), col, false, 0.0f);
|
||||
}
|
||||
RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding);
|
||||
}
|
||||
|
||||
|
|
@ -7851,6 +7867,55 @@ void ImGui::EndMenu()
|
|||
EndPopup();
|
||||
}
|
||||
|
||||
bool ImGui::BBLMenuItem(const char* label, const char* shortcut, bool selected, bool enabled)
|
||||
{
|
||||
ImGuiWindow* window = GetCurrentWindow();
|
||||
if (window->SkipItems)
|
||||
return false;
|
||||
|
||||
ImGuiContext& g = *GImGui;
|
||||
ImGuiStyle& style = g.Style;
|
||||
ImVec2 pos = window->DC.CursorPos;
|
||||
ImVec2 label_size = CalcTextSize(label, NULL, true);
|
||||
|
||||
// We've been using the equivalent of ImGuiSelectableFlags_SetNavIdOnHover on all Selectable() since early Nav system days (commit 43ee5d73),
|
||||
// but I am unsure whether this should be kept at all. For now moved it to be an opt-in feature used by menus only.
|
||||
ImGuiSelectableFlags flags = ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_SetNavIdOnHover | (enabled ? 0 : ImGuiSelectableFlags_Disabled);
|
||||
bool pressed;
|
||||
if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
|
||||
{
|
||||
// Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful
|
||||
// Note that in this situation: we don't render the shortcut, we render a highlight instead of the selected tick mark.
|
||||
float w = label_size.x;
|
||||
window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * 0.5f);
|
||||
PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y));
|
||||
pressed = BBLSelectable(label, selected, flags, ImVec2(w, 0.0f));
|
||||
PopStyleVar();
|
||||
window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().
|
||||
}
|
||||
else
|
||||
{
|
||||
// Menu item inside a vertical menu
|
||||
// (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f.
|
||||
// Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system.
|
||||
float shortcut_w = shortcut ? CalcTextSize(shortcut, NULL).x : 0.0f;
|
||||
float min_w = window->DC.MenuColumns.DeclColumns(label_size.x, shortcut_w, IM_FLOOR(g.FontSize * 1.20f)); // Feedback for next frame
|
||||
float extra_w = ImMax(0.0f, GetContentRegionAvail().x - min_w);
|
||||
pressed = BBLSelectable(label, false, flags | ImGuiSelectableFlags_SpanAvailWidth | ImGuiComboFlags_NoArrowButton, ImVec2(min_w, 0.0f));
|
||||
if (shortcut_w > 0.0f)
|
||||
{
|
||||
PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
|
||||
RenderText(pos + ImVec2(window->DC.MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false);
|
||||
PopStyleColor();
|
||||
}
|
||||
if (selected)
|
||||
RenderCheckMark(window->DrawList, pos + ImVec2(window->DC.MenuColumns.Pos[2] + extra_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled), g.FontSize * 0.866f);
|
||||
}
|
||||
|
||||
IMGUI_TEST_ENGINE_ITEM_INFO(window->DC.LastItemId, label, window->DC.LastItemStatusFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0));
|
||||
return pressed;
|
||||
}
|
||||
|
||||
bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled)
|
||||
{
|
||||
ImGuiWindow* window = GetCurrentWindow();
|
||||
|
|
|
|||
|
|
@ -922,27 +922,28 @@ private:
|
|||
|
||||
#ifdef SVGTOOLS_HPP
|
||||
svg::SVGWriter<RawShape> svgwriter;
|
||||
svgwriter.setSize(binbb);
|
||||
svgwriter.writeShape(box2RawShape(binbb), "none", "black");
|
||||
for (int i = 0; i < nfps.size(); i++)
|
||||
svgwriter.writeShape(nfps[i], "none", "blue");
|
||||
for (int i = 0; i < items_.size(); i++)
|
||||
svgwriter.writeItem(items_[i], "none", "black");
|
||||
//for (int i = 0; i < merged_pile_.size(); i++)
|
||||
// svgwriter.writeShape(merged_pile_[i], "none", "yellow");
|
||||
for (int i = 0; i < merged_pile_.size(); i++)
|
||||
svgwriter.writeShape(merged_pile_[i], "none", "yellow");
|
||||
svgwriter.writeItem(item, "none", "red", 2);
|
||||
|
||||
svgwriter.writeItem(item, "none", "red");
|
||||
std::stringstream ss;
|
||||
ss.setf(std::ios::fixed | std::ios::showpoint);
|
||||
ss.precision(1);
|
||||
ss << "trans=" << round(item.translation().x() / 1e6) << "," << round(item.translation().y() / 1e6)
|
||||
<< "-rot=" << round(item.rotation().toDegrees())
|
||||
<< "-score=" << round(global_score);
|
||||
ss << "t=" << round(item.translation().x() / 1e6) << "," << round(item.translation().y() / 1e6)
|
||||
//<< "-rot=" << round(item.rotation().toDegrees())
|
||||
<< "-sco=" << round(global_score);
|
||||
svgwriter.draw_text(20, 20, ss.str(), "blue", 20);
|
||||
ss.str("");
|
||||
ss << "items.size=" << items_.size()
|
||||
<< "-merged_pile.size=" << merged_pile_.size();
|
||||
svgwriter.draw_text(20, 40, ss.str(), "blue", 20);
|
||||
svgwriter.save("SVG/nfpplacer_" + std::to_string(plate_id) + "_" + item.name + "_" + ss.str());
|
||||
svgwriter.save(boost::filesystem::path("SVG")/ ("nfpplacer_" + std::to_string(plate_id) + "_" + ss.str() + "_" + item.name + ".svg"));
|
||||
#endif
|
||||
|
||||
if(can_pack) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <libnest2d/nester.hpp>
|
||||
|
||||
namespace libnest2d { namespace svg {
|
||||
|
|
@ -27,9 +28,10 @@ public:
|
|||
OrigoLocation origo_location;
|
||||
Coord mm_in_coord_units;
|
||||
double width, height;
|
||||
double x0, y0;
|
||||
Config():
|
||||
origo_location(BOTTOMLEFT), mm_in_coord_units(1000000),
|
||||
width(500), height(500) {}
|
||||
width(500), height(500),x0(100) {}
|
||||
|
||||
};
|
||||
|
||||
|
|
@ -42,10 +44,12 @@ public:
|
|||
SVGWriter(const Config& conf = Config()):
|
||||
conf_(conf) {}
|
||||
|
||||
void setSize(const Box& box) {
|
||||
conf_.height = static_cast<double>(box.height()) /
|
||||
void setSize(const Box &box) {
|
||||
conf_.x0 = box.width() / 5;
|
||||
conf_.x0 = box.height() / 5;
|
||||
conf_.height = static_cast<double>(box.height() + conf_.y0*2) /
|
||||
conf_.mm_in_coord_units;
|
||||
conf_.width = static_cast<double>(box.width()) /
|
||||
conf_.width = static_cast<double>(box.width() + conf_.x0*2) /
|
||||
conf_.mm_in_coord_units;
|
||||
}
|
||||
|
||||
|
|
@ -56,11 +60,17 @@ public:
|
|||
std::round(conf_.height*conf_.mm_in_coord_units) );
|
||||
|
||||
auto& contour = shapelike::contour(tsh);
|
||||
for(auto& v : contour) setY(v, -getY(v) + d);
|
||||
for (auto &v : contour) {
|
||||
setX(v, getX(v) + conf_.x0); // right shift so we can draw outside the bounding box
|
||||
setY(v, -getY(v) + d + conf_.y0);
|
||||
}
|
||||
|
||||
auto& holes = shapelike::holes(tsh);
|
||||
for(auto& h : holes) for(auto& v : h) setY(v, -getY(v) + d);
|
||||
|
||||
for (auto &h : holes)
|
||||
for (auto &v : h) {
|
||||
setX(v, getX(v) + conf_.x0);
|
||||
setY(v, -getY(v) + d + conf_.y0);
|
||||
}
|
||||
}
|
||||
currentLayer() +=
|
||||
shapelike::serialize<Formats::SVG>(tsh,
|
||||
|
|
@ -126,6 +136,22 @@ public:
|
|||
};
|
||||
}
|
||||
|
||||
// save svg in utf-8 file name
|
||||
void save(const boost::filesystem::path &filepath)
|
||||
{
|
||||
size_t lyrc = svg_layers_.size() > 1 ? 1 : 0;
|
||||
size_t last = svg_layers_.size() > 1 ? svg_layers_.size() : 0;
|
||||
|
||||
for (auto &lyr : svg_layers_) {
|
||||
boost::filesystem::ofstream out(filepath, std::fstream::out);
|
||||
if (out.is_open()) out << lyr;
|
||||
if (lyrc == last && !finished_) out << "\n</svg>\n";
|
||||
out.flush();
|
||||
out.close();
|
||||
lyrc++;
|
||||
};
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
std::string& currentLayer() { return svg_layers_.back(); }
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ static const std::string MODELS_STR = "models";
|
|||
const std::string AppConfig::SECTION_FILAMENTS = "filaments";
|
||||
const std::string AppConfig::SECTION_MATERIALS = "sla_materials";
|
||||
|
||||
std::string AppConfig::get_langauge_code()
|
||||
std::string AppConfig::get_language_code()
|
||||
{
|
||||
std::string get_lang = get("language");
|
||||
if (get_lang.empty()) return "";
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ public:
|
|||
this->reset();
|
||||
}
|
||||
|
||||
std::string get_langauge_code();
|
||||
std::string get_language_code();
|
||||
std::string get_hms_host();
|
||||
|
||||
// Clear and reset to defaults.
|
||||
|
|
|
|||
79
src/libslic3r/Arachne/BeadingStrategy/BeadingStrategy.cpp
Normal file
79
src/libslic3r/Arachne/BeadingStrategy/BeadingStrategy.cpp
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "BeadingStrategy.hpp"
|
||||
#include "Point.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
BeadingStrategy::BeadingStrategy(coord_t optimal_width, double wall_split_middle_threshold, double wall_add_middle_threshold, coord_t default_transition_length, float transitioning_angle)
|
||||
: optimal_width(optimal_width)
|
||||
, wall_split_middle_threshold(wall_split_middle_threshold)
|
||||
, wall_add_middle_threshold(wall_add_middle_threshold)
|
||||
, default_transition_length(default_transition_length)
|
||||
, transitioning_angle(transitioning_angle)
|
||||
{
|
||||
name = "Unknown";
|
||||
}
|
||||
|
||||
BeadingStrategy::BeadingStrategy(const BeadingStrategy &other)
|
||||
: optimal_width(other.optimal_width)
|
||||
, wall_split_middle_threshold(other.wall_split_middle_threshold)
|
||||
, wall_add_middle_threshold(other.wall_add_middle_threshold)
|
||||
, default_transition_length(other.default_transition_length)
|
||||
, transitioning_angle(other.transitioning_angle)
|
||||
, name(other.name)
|
||||
{}
|
||||
|
||||
coord_t BeadingStrategy::getTransitioningLength(coord_t lower_bead_count) const
|
||||
{
|
||||
if (lower_bead_count == 0)
|
||||
return scaled<coord_t>(0.01);
|
||||
return default_transition_length;
|
||||
}
|
||||
|
||||
float BeadingStrategy::getTransitionAnchorPos(coord_t lower_bead_count) const
|
||||
{
|
||||
coord_t lower_optimum = getOptimalThickness(lower_bead_count);
|
||||
coord_t transition_point = getTransitionThickness(lower_bead_count);
|
||||
coord_t upper_optimum = getOptimalThickness(lower_bead_count + 1);
|
||||
return 1.0 - float(transition_point - lower_optimum) / float(upper_optimum - lower_optimum);
|
||||
}
|
||||
|
||||
std::vector<coord_t> BeadingStrategy::getNonlinearThicknesses(coord_t lower_bead_count) const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string BeadingStrategy::toString() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
double BeadingStrategy::getSplitMiddleThreshold() const
|
||||
{
|
||||
return wall_split_middle_threshold;
|
||||
}
|
||||
|
||||
double BeadingStrategy::getTransitioningAngle() const
|
||||
{
|
||||
return transitioning_angle;
|
||||
}
|
||||
|
||||
coord_t BeadingStrategy::getOptimalThickness(coord_t bead_count) const
|
||||
{
|
||||
return optimal_width * bead_count;
|
||||
}
|
||||
|
||||
coord_t BeadingStrategy::getTransitionThickness(coord_t lower_bead_count) const
|
||||
{
|
||||
const coord_t lower_ideal_width = getOptimalThickness(lower_bead_count);
|
||||
const coord_t higher_ideal_width = getOptimalThickness(lower_bead_count + 1);
|
||||
const double threshold = lower_bead_count % 2 == 1 ? wall_split_middle_threshold : wall_add_middle_threshold;
|
||||
return lower_ideal_width + threshold * (higher_ideal_width - lower_ideal_width);
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
117
src/libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp
Normal file
117
src/libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
// Copyright (c) 2022 Ultimaker B.V.
|
||||
// CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef BEADING_STRATEGY_H
|
||||
#define BEADING_STRATEGY_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "../../libslic3r.h"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
template<typename T> constexpr T pi_div(const T div) { return static_cast<T>(M_PI) / div; }
|
||||
|
||||
/*!
|
||||
* Mostly virtual base class template.
|
||||
*
|
||||
* Strategy for covering a given (constant) horizontal model thickness with a number of beads.
|
||||
*
|
||||
* The beads may have different widths.
|
||||
*
|
||||
* TODO: extend with printing order?
|
||||
*/
|
||||
class BeadingStrategy
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* The beading for a given horizontal model thickness.
|
||||
*/
|
||||
struct Beading
|
||||
{
|
||||
coord_t total_thickness;
|
||||
std::vector<coord_t> bead_widths; //! The line width of each bead from the outer inset inward
|
||||
std::vector<coord_t> toolpath_locations; //! The distance of the toolpath location of each bead from the outline
|
||||
coord_t left_over; //! The distance not covered by any bead; gap area.
|
||||
};
|
||||
|
||||
BeadingStrategy(coord_t optimal_width, double wall_split_middle_threshold, double wall_add_middle_threshold, coord_t default_transition_length, float transitioning_angle = pi_div(3));
|
||||
|
||||
BeadingStrategy(const BeadingStrategy &other);
|
||||
|
||||
virtual ~BeadingStrategy() = default;
|
||||
|
||||
/*!
|
||||
* Retrieve the bead widths with which to cover a given thickness.
|
||||
*
|
||||
* Requirement: Given a constant \p bead_count the output of each bead width must change gradually along with the \p thickness.
|
||||
*
|
||||
* \note The \p bead_count might be different from the \ref BeadingStrategy::optimal_bead_count
|
||||
*/
|
||||
virtual Beading compute(coord_t thickness, coord_t bead_count) const = 0;
|
||||
|
||||
/*!
|
||||
* The ideal thickness for a given \param bead_count
|
||||
*/
|
||||
virtual coord_t getOptimalThickness(coord_t bead_count) const;
|
||||
|
||||
/*!
|
||||
* The model thickness at which \ref BeadingStrategy::optimal_bead_count transitions from \p lower_bead_count to \p lower_bead_count + 1
|
||||
*/
|
||||
virtual coord_t getTransitionThickness(coord_t lower_bead_count) const;
|
||||
|
||||
/*!
|
||||
* The number of beads should we ideally usefor a given model thickness
|
||||
*/
|
||||
virtual coord_t getOptimalBeadCount(coord_t thickness) const = 0;
|
||||
|
||||
/*!
|
||||
* The length of the transitioning region along the marked / significant regions of the skeleton.
|
||||
*
|
||||
* Transitions are used to smooth out the jumps in integer bead count; the jumps turn into ramps with some incline defined by their length.
|
||||
*/
|
||||
virtual coord_t getTransitioningLength(coord_t lower_bead_count) const;
|
||||
|
||||
/*!
|
||||
* The fraction of the transition length to put between the lower end of the transition and the point where the unsmoothed bead count jumps.
|
||||
*
|
||||
* Transitions are used to smooth out the jumps in integer bead count; the jumps turn into ramps which could be positioned relative to the jump location.
|
||||
*/
|
||||
virtual float getTransitionAnchorPos(coord_t lower_bead_count) const;
|
||||
|
||||
/*!
|
||||
* Get the locations in a bead count region where \ref BeadingStrategy::compute exhibits a bend in the widths.
|
||||
* Ordered from lower thickness to higher.
|
||||
*
|
||||
* This is used to insert extra support bones into the skeleton, so that the resulting beads in long trapezoids don't linearly change between the two ends.
|
||||
*/
|
||||
virtual std::vector<coord_t> getNonlinearThicknesses(coord_t lower_bead_count) const;
|
||||
|
||||
virtual std::string toString() const;
|
||||
|
||||
double getSplitMiddleThreshold() const;
|
||||
double getTransitioningAngle() const;
|
||||
|
||||
protected:
|
||||
std::string name;
|
||||
|
||||
coord_t optimal_width; //! Optimal bead width, nominal width off the walls in 'ideal' circumstances.
|
||||
|
||||
double wall_split_middle_threshold; //! Threshold when a middle wall should be split into two, as a ratio of the optimal wall width.
|
||||
|
||||
double wall_add_middle_threshold; //! Threshold when a new middle wall should be added between an even number of walls, as a ratio of the optimal wall width.
|
||||
|
||||
coord_t default_transition_length; //! The length of the region to smoothly transfer between bead counts
|
||||
|
||||
/*!
|
||||
* The maximum angle between outline segments smaller than which we are going to add transitions
|
||||
* Equals 180 - the "limit bisector angle" from the paper
|
||||
*/
|
||||
double transitioning_angle;
|
||||
};
|
||||
|
||||
using BeadingStrategyPtr = std::unique_ptr<BeadingStrategy>;
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // BEADING_STRATEGY_H
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "BeadingStrategyFactory.hpp"
|
||||
|
||||
#include "LimitedBeadingStrategy.hpp"
|
||||
#include "WideningBeadingStrategy.hpp"
|
||||
#include "DistributedBeadingStrategy.hpp"
|
||||
#include "RedistributeBeadingStrategy.hpp"
|
||||
#include "OuterWallInsetBeadingStrategy.hpp"
|
||||
|
||||
#include <limits>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
BeadingStrategyPtr BeadingStrategyFactory::makeStrategy(
|
||||
const coord_t preferred_bead_width_outer,
|
||||
const coord_t preferred_bead_width_inner,
|
||||
const coord_t preferred_transition_length,
|
||||
const float transitioning_angle,
|
||||
const bool print_thin_walls,
|
||||
const coord_t min_bead_width,
|
||||
const coord_t min_feature_size,
|
||||
const double wall_split_middle_threshold,
|
||||
const double wall_add_middle_threshold,
|
||||
const coord_t max_bead_count,
|
||||
const coord_t outer_wall_offset,
|
||||
const int inward_distributed_center_wall_count,
|
||||
const double minimum_variable_line_ratio
|
||||
)
|
||||
{
|
||||
BeadingStrategyPtr ret = std::make_unique<DistributedBeadingStrategy>(preferred_bead_width_inner, preferred_transition_length, transitioning_angle, wall_split_middle_threshold, wall_add_middle_threshold, inward_distributed_center_wall_count);
|
||||
BOOST_LOG_TRIVIAL(debug) << "Applying the Redistribute meta-strategy with outer-wall width = " << preferred_bead_width_outer << ", inner-wall width = " << preferred_bead_width_inner << ".";
|
||||
ret = std::make_unique<RedistributeBeadingStrategy>(preferred_bead_width_outer, minimum_variable_line_ratio, std::move(ret));
|
||||
|
||||
if (print_thin_walls) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "Applying the Widening Beading meta-strategy with minimum input width " << min_feature_size << " and minimum output width " << min_bead_width << ".";
|
||||
ret = std::make_unique<WideningBeadingStrategy>(std::move(ret), min_feature_size, min_bead_width);
|
||||
}
|
||||
if (outer_wall_offset > 0) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "Applying the OuterWallOffset meta-strategy with offset = " << outer_wall_offset << ".";
|
||||
ret = std::make_unique<OuterWallInsetBeadingStrategy>(outer_wall_offset, std::move(ret));
|
||||
}
|
||||
|
||||
//Apply the LimitedBeadingStrategy last, since that adds a 0-width marker wall which other beading strategies shouldn't touch.
|
||||
BOOST_LOG_TRIVIAL(debug) << "Applying the Limited Beading meta-strategy with maximum bead count = " << max_bead_count << ".";
|
||||
ret = std::make_unique<LimitedBeadingStrategy>(max_bead_count, std::move(ret));
|
||||
return ret;
|
||||
}
|
||||
} // namespace Slic3r::Arachne
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright (c) 2022 Ultimaker B.V.
|
||||
// CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef BEADING_STRATEGY_FACTORY_H
|
||||
#define BEADING_STRATEGY_FACTORY_H
|
||||
|
||||
#include "BeadingStrategy.hpp"
|
||||
#include "../../Point.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
class BeadingStrategyFactory
|
||||
{
|
||||
public:
|
||||
static BeadingStrategyPtr makeStrategy
|
||||
(
|
||||
coord_t preferred_bead_width_outer = scaled<coord_t>(0.0005),
|
||||
coord_t preferred_bead_width_inner = scaled<coord_t>(0.0005),
|
||||
coord_t preferred_transition_length = scaled<coord_t>(0.0004),
|
||||
float transitioning_angle = M_PI / 4.0,
|
||||
bool print_thin_walls = false,
|
||||
coord_t min_bead_width = 0,
|
||||
coord_t min_feature_size = 0,
|
||||
double wall_split_middle_threshold = 0.5,
|
||||
double wall_add_middle_threshold = 0.5,
|
||||
coord_t max_bead_count = 0,
|
||||
coord_t outer_wall_offset = 0,
|
||||
int inward_distributed_center_wall_count = 2,
|
||||
double minimum_variable_line_width = 0.5
|
||||
);
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // BEADING_STRATEGY_FACTORY_H
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
// Copyright (c) 2022 Ultimaker B.V.
|
||||
// CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
#include <numeric>
|
||||
#include "DistributedBeadingStrategy.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
DistributedBeadingStrategy::DistributedBeadingStrategy(const coord_t optimal_width,
|
||||
const coord_t default_transition_length,
|
||||
const double transitioning_angle,
|
||||
const double wall_split_middle_threshold,
|
||||
const double wall_add_middle_threshold,
|
||||
const int distribution_radius)
|
||||
: BeadingStrategy(optimal_width, wall_split_middle_threshold, wall_add_middle_threshold, default_transition_length, transitioning_angle)
|
||||
{
|
||||
if(distribution_radius >= 2)
|
||||
one_over_distribution_radius_squared = 1.0f / (distribution_radius - 1) * 1.0f / (distribution_radius - 1);
|
||||
else
|
||||
one_over_distribution_radius_squared = 1.0f / 1 * 1.0f / 1;
|
||||
name = "DistributedBeadingStrategy";
|
||||
}
|
||||
|
||||
DistributedBeadingStrategy::Beading DistributedBeadingStrategy::compute(coord_t thickness, coord_t bead_count) const
|
||||
{
|
||||
Beading ret;
|
||||
|
||||
ret.total_thickness = thickness;
|
||||
if (bead_count > 2) {
|
||||
const coord_t to_be_divided = thickness - bead_count * optimal_width;
|
||||
const float middle = static_cast<float>(bead_count - 1) / 2;
|
||||
|
||||
const auto getWeight = [middle, this](coord_t bead_idx) {
|
||||
const float dev_from_middle = bead_idx - middle;
|
||||
return std::max(0.0f, 1.0f - one_over_distribution_radius_squared * dev_from_middle * dev_from_middle);
|
||||
};
|
||||
|
||||
std::vector<float> weights;
|
||||
weights.resize(bead_count);
|
||||
for (coord_t bead_idx = 0; bead_idx < bead_count; bead_idx++)
|
||||
weights[bead_idx] = getWeight(bead_idx);
|
||||
|
||||
const float total_weight = std::accumulate(weights.cbegin(), weights.cend(), 0.f);
|
||||
for (coord_t bead_idx = 0; bead_idx < bead_count; bead_idx++) {
|
||||
const float weight_fraction = weights[bead_idx] / total_weight;
|
||||
const coord_t splitup_left_over_weight = to_be_divided * weight_fraction;
|
||||
const coord_t width = optimal_width + splitup_left_over_weight;
|
||||
if (bead_idx == 0)
|
||||
ret.toolpath_locations.emplace_back(width / 2);
|
||||
else
|
||||
ret.toolpath_locations.emplace_back(ret.toolpath_locations.back() + (ret.bead_widths.back() + width) / 2);
|
||||
ret.bead_widths.emplace_back(width);
|
||||
}
|
||||
ret.left_over = 0;
|
||||
} else if (bead_count == 2) {
|
||||
const coord_t outer_width = thickness / 2;
|
||||
ret.bead_widths.emplace_back(outer_width);
|
||||
ret.bead_widths.emplace_back(outer_width);
|
||||
ret.toolpath_locations.emplace_back(outer_width / 2);
|
||||
ret.toolpath_locations.emplace_back(thickness - outer_width / 2);
|
||||
ret.left_over = 0;
|
||||
} else if (bead_count == 1) {
|
||||
const coord_t outer_width = thickness;
|
||||
ret.bead_widths.emplace_back(outer_width);
|
||||
ret.toolpath_locations.emplace_back(outer_width / 2);
|
||||
ret.left_over = 0;
|
||||
} else {
|
||||
ret.left_over = thickness;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
coord_t DistributedBeadingStrategy::getOptimalBeadCount(coord_t thickness) const
|
||||
{
|
||||
const coord_t naive_count = thickness / optimal_width; // How many lines we can fit in for sure.
|
||||
const coord_t remainder = thickness - naive_count * optimal_width; // Space left after fitting that many lines.
|
||||
const coord_t minimum_line_width = optimal_width * (naive_count % 2 == 1 ? wall_split_middle_threshold : wall_add_middle_threshold);
|
||||
return naive_count + (remainder >= minimum_line_width); // If there's enough space, fit an extra one.
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright (c) 2022 Ultimaker B.V.
|
||||
// CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef DISTRIBUTED_BEADING_STRATEGY_H
|
||||
#define DISTRIBUTED_BEADING_STRATEGY_H
|
||||
|
||||
#include "BeadingStrategy.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
/*!
|
||||
* This beading strategy chooses a wall count that would make the line width
|
||||
* deviate the least from the optimal line width, and then distributes the lines
|
||||
* evenly among the thickness available.
|
||||
*/
|
||||
class DistributedBeadingStrategy : public BeadingStrategy
|
||||
{
|
||||
protected:
|
||||
float one_over_distribution_radius_squared; // (1 / distribution_radius)^2
|
||||
|
||||
public:
|
||||
/*!
|
||||
* \param distribution_radius the radius (in number of beads) over which to distribute the discrepancy between the feature size and the optimal thickness
|
||||
*/
|
||||
DistributedBeadingStrategy(coord_t optimal_width,
|
||||
coord_t default_transition_length,
|
||||
double transitioning_angle,
|
||||
double wall_split_middle_threshold,
|
||||
double wall_add_middle_threshold,
|
||||
int distribution_radius);
|
||||
|
||||
~DistributedBeadingStrategy() override = default;
|
||||
|
||||
Beading compute(coord_t thickness, coord_t bead_count) const override;
|
||||
coord_t getOptimalBeadCount(coord_t thickness) const override;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // DISTRIBUTED_BEADING_STRATEGY_H
|
||||
126
src/libslic3r/Arachne/BeadingStrategy/LimitedBeadingStrategy.cpp
Normal file
126
src/libslic3r/Arachne/BeadingStrategy/LimitedBeadingStrategy.cpp
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include <cassert>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include "LimitedBeadingStrategy.hpp"
|
||||
#include "Point.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
std::string LimitedBeadingStrategy::toString() const
|
||||
{
|
||||
return std::string("LimitedBeadingStrategy+") + parent->toString();
|
||||
}
|
||||
|
||||
coord_t LimitedBeadingStrategy::getTransitioningLength(coord_t lower_bead_count) const
|
||||
{
|
||||
return parent->getTransitioningLength(lower_bead_count);
|
||||
}
|
||||
|
||||
float LimitedBeadingStrategy::getTransitionAnchorPos(coord_t lower_bead_count) const
|
||||
{
|
||||
return parent->getTransitionAnchorPos(lower_bead_count);
|
||||
}
|
||||
|
||||
LimitedBeadingStrategy::LimitedBeadingStrategy(const coord_t max_bead_count, BeadingStrategyPtr parent)
|
||||
: BeadingStrategy(*parent)
|
||||
, max_bead_count(max_bead_count)
|
||||
, parent(std::move(parent))
|
||||
{
|
||||
if (max_bead_count % 2 == 1)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(warning) << "LimitedBeadingStrategy with odd bead count is odd indeed!";
|
||||
}
|
||||
}
|
||||
|
||||
LimitedBeadingStrategy::Beading LimitedBeadingStrategy::compute(coord_t thickness, coord_t bead_count) const
|
||||
{
|
||||
if (bead_count <= max_bead_count)
|
||||
{
|
||||
Beading ret = parent->compute(thickness, bead_count);
|
||||
bead_count = ret.toolpath_locations.size();
|
||||
|
||||
if (bead_count % 2 == 0 && bead_count == max_bead_count)
|
||||
{
|
||||
const coord_t innermost_toolpath_location = ret.toolpath_locations[max_bead_count / 2 - 1];
|
||||
const coord_t innermost_toolpath_width = ret.bead_widths[max_bead_count / 2 - 1];
|
||||
ret.toolpath_locations.insert(ret.toolpath_locations.begin() + max_bead_count / 2, innermost_toolpath_location + innermost_toolpath_width / 2);
|
||||
ret.bead_widths.insert(ret.bead_widths.begin() + max_bead_count / 2, 0);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
assert(bead_count == max_bead_count + 1);
|
||||
if(bead_count != max_bead_count + 1)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(warning) << "Too many beads! " << bead_count << " != " << max_bead_count + 1;
|
||||
}
|
||||
|
||||
coord_t optimal_thickness = parent->getOptimalThickness(max_bead_count);
|
||||
Beading ret = parent->compute(optimal_thickness, max_bead_count);
|
||||
bead_count = ret.toolpath_locations.size();
|
||||
ret.left_over += thickness - ret.total_thickness;
|
||||
ret.total_thickness = thickness;
|
||||
|
||||
// Enforce symmetry
|
||||
if (bead_count % 2 == 1) {
|
||||
ret.toolpath_locations[bead_count / 2] = thickness / 2;
|
||||
ret.bead_widths[bead_count / 2] = thickness - optimal_thickness;
|
||||
}
|
||||
for (coord_t bead_idx = 0; bead_idx < (bead_count + 1) / 2; bead_idx++)
|
||||
ret.toolpath_locations[bead_count - 1 - bead_idx] = thickness - ret.toolpath_locations[bead_idx];
|
||||
|
||||
//Create a "fake" inner wall with 0 width to indicate the edge of the walled area.
|
||||
//This wall can then be used by other structures to e.g. fill the infill area adjacent to the variable-width walls.
|
||||
coord_t innermost_toolpath_location = ret.toolpath_locations[max_bead_count / 2 - 1];
|
||||
coord_t innermost_toolpath_width = ret.bead_widths[max_bead_count / 2 - 1];
|
||||
ret.toolpath_locations.insert(ret.toolpath_locations.begin() + max_bead_count / 2, innermost_toolpath_location + innermost_toolpath_width / 2);
|
||||
ret.bead_widths.insert(ret.bead_widths.begin() + max_bead_count / 2, 0);
|
||||
|
||||
//Symmetry on both sides. Symmetry is guaranteed since this code is stopped early if the bead_count <= max_bead_count, and never reaches this point then.
|
||||
const size_t opposite_bead = bead_count - (max_bead_count / 2 - 1);
|
||||
innermost_toolpath_location = ret.toolpath_locations[opposite_bead];
|
||||
innermost_toolpath_width = ret.bead_widths[opposite_bead];
|
||||
ret.toolpath_locations.insert(ret.toolpath_locations.begin() + opposite_bead, innermost_toolpath_location - innermost_toolpath_width / 2);
|
||||
ret.bead_widths.insert(ret.bead_widths.begin() + opposite_bead, 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
coord_t LimitedBeadingStrategy::getOptimalThickness(coord_t bead_count) const
|
||||
{
|
||||
if (bead_count <= max_bead_count)
|
||||
return parent->getOptimalThickness(bead_count);
|
||||
assert(false);
|
||||
return scaled<coord_t>(1000.); // 1 meter (Cura was returning 10 meter)
|
||||
}
|
||||
|
||||
coord_t LimitedBeadingStrategy::getTransitionThickness(coord_t lower_bead_count) const
|
||||
{
|
||||
if (lower_bead_count < max_bead_count)
|
||||
return parent->getTransitionThickness(lower_bead_count);
|
||||
|
||||
if (lower_bead_count == max_bead_count)
|
||||
return parent->getOptimalThickness(lower_bead_count + 1) - scaled<coord_t>(0.01);
|
||||
|
||||
assert(false);
|
||||
return scaled<coord_t>(900.); // 0.9 meter;
|
||||
}
|
||||
|
||||
coord_t LimitedBeadingStrategy::getOptimalBeadCount(coord_t thickness) const
|
||||
{
|
||||
coord_t parent_bead_count = parent->getOptimalBeadCount(thickness);
|
||||
if (parent_bead_count <= max_bead_count) {
|
||||
return parent->getOptimalBeadCount(thickness);
|
||||
} else if (parent_bead_count == max_bead_count + 1) {
|
||||
if (thickness < parent->getOptimalThickness(max_bead_count + 1) - scaled<coord_t>(0.01))
|
||||
return max_bead_count;
|
||||
else
|
||||
return max_bead_count + 1;
|
||||
}
|
||||
else return max_bead_count + 1;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef LIMITED_BEADING_STRATEGY_H
|
||||
#define LIMITED_BEADING_STRATEGY_H
|
||||
|
||||
#include "BeadingStrategy.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
/*!
|
||||
* This is a meta-strategy that can be applied on top of any other beading
|
||||
* strategy, which limits the thickness of the walls to the thickness that the
|
||||
* lines can reasonably print.
|
||||
*
|
||||
* The width of the wall is limited to the maximum number of contours times the
|
||||
* maximum width of each of these contours.
|
||||
*
|
||||
* If the width of the wall gets limited, this strategy outputs one additional
|
||||
* bead with 0 width. This bead is used to denote the limits of the walled area.
|
||||
* Other structures can then use this border to align their structures to, such
|
||||
* as to create correctly overlapping infill or skin, or to align the infill
|
||||
* pattern to any extra infill walls.
|
||||
*/
|
||||
class LimitedBeadingStrategy : public BeadingStrategy
|
||||
{
|
||||
public:
|
||||
LimitedBeadingStrategy(coord_t max_bead_count, BeadingStrategyPtr parent);
|
||||
|
||||
~LimitedBeadingStrategy() override = default;
|
||||
|
||||
Beading compute(coord_t thickness, coord_t bead_count) const override;
|
||||
coord_t getOptimalThickness(coord_t bead_count) const override;
|
||||
coord_t getTransitionThickness(coord_t lower_bead_count) const override;
|
||||
coord_t getOptimalBeadCount(coord_t thickness) const override;
|
||||
std::string toString() const override;
|
||||
|
||||
coord_t getTransitioningLength(coord_t lower_bead_count) const override;
|
||||
|
||||
float getTransitionAnchorPos(coord_t lower_bead_count) const override;
|
||||
|
||||
protected:
|
||||
const coord_t max_bead_count;
|
||||
const BeadingStrategyPtr parent;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // LIMITED_DISTRIBUTED_BEADING_STRATEGY_H
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "OuterWallInsetBeadingStrategy.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
OuterWallInsetBeadingStrategy::OuterWallInsetBeadingStrategy(coord_t outer_wall_offset, BeadingStrategyPtr parent)
|
||||
: BeadingStrategy(*parent), parent(std::move(parent)), outer_wall_offset(outer_wall_offset)
|
||||
{
|
||||
name = "OuterWallOfsetBeadingStrategy";
|
||||
}
|
||||
|
||||
coord_t OuterWallInsetBeadingStrategy::getOptimalThickness(coord_t bead_count) const
|
||||
{
|
||||
return parent->getOptimalThickness(bead_count);
|
||||
}
|
||||
|
||||
coord_t OuterWallInsetBeadingStrategy::getTransitionThickness(coord_t lower_bead_count) const
|
||||
{
|
||||
return parent->getTransitionThickness(lower_bead_count);
|
||||
}
|
||||
|
||||
coord_t OuterWallInsetBeadingStrategy::getOptimalBeadCount(coord_t thickness) const
|
||||
{
|
||||
return parent->getOptimalBeadCount(thickness);
|
||||
}
|
||||
|
||||
coord_t OuterWallInsetBeadingStrategy::getTransitioningLength(coord_t lower_bead_count) const
|
||||
{
|
||||
return parent->getTransitioningLength(lower_bead_count);
|
||||
}
|
||||
|
||||
std::string OuterWallInsetBeadingStrategy::toString() const
|
||||
{
|
||||
return std::string("OuterWallOfsetBeadingStrategy+") + parent->toString();
|
||||
}
|
||||
|
||||
BeadingStrategy::Beading OuterWallInsetBeadingStrategy::compute(coord_t thickness, coord_t bead_count) const
|
||||
{
|
||||
Beading ret = parent->compute(thickness, bead_count);
|
||||
|
||||
// Actual count and thickness as represented by extant walls. Don't count any potential zero-width 'signaling' walls.
|
||||
bead_count = std::count_if(ret.bead_widths.begin(), ret.bead_widths.end(), [](const coord_t width) { return width > 0; });
|
||||
|
||||
// No need to apply any inset if there is just a single wall.
|
||||
if (bead_count < 2)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Actually move the outer wall inside. Ensure that the outer wall never goes beyond the middle line.
|
||||
ret.toolpath_locations[0] = std::min(ret.toolpath_locations[0] + outer_wall_offset, thickness / 2);
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef OUTER_WALL_INSET_BEADING_STRATEGY_H
|
||||
#define OUTER_WALL_INSET_BEADING_STRATEGY_H
|
||||
|
||||
#include "BeadingStrategy.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
/*
|
||||
* This is a meta strategy that allows for the outer wall to be inset towards the inside of the model.
|
||||
*/
|
||||
class OuterWallInsetBeadingStrategy : public BeadingStrategy
|
||||
{
|
||||
public:
|
||||
OuterWallInsetBeadingStrategy(coord_t outer_wall_offset, BeadingStrategyPtr parent);
|
||||
|
||||
~OuterWallInsetBeadingStrategy() override = default;
|
||||
|
||||
Beading compute(coord_t thickness, coord_t bead_count) const override;
|
||||
|
||||
coord_t getOptimalThickness(coord_t bead_count) const override;
|
||||
coord_t getTransitionThickness(coord_t lower_bead_count) const override;
|
||||
coord_t getOptimalBeadCount(coord_t thickness) const override;
|
||||
coord_t getTransitioningLength(coord_t lower_bead_count) const override;
|
||||
|
||||
std::string toString() const override;
|
||||
|
||||
private:
|
||||
BeadingStrategyPtr parent;
|
||||
coord_t outer_wall_offset;
|
||||
};
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // OUTER_WALL_INSET_BEADING_STRATEGY_H
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "RedistributeBeadingStrategy.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
RedistributeBeadingStrategy::RedistributeBeadingStrategy(const coord_t optimal_width_outer,
|
||||
const double minimum_variable_line_ratio,
|
||||
BeadingStrategyPtr parent)
|
||||
: BeadingStrategy(*parent)
|
||||
, parent(std::move(parent))
|
||||
, optimal_width_outer(optimal_width_outer)
|
||||
, minimum_variable_line_ratio(minimum_variable_line_ratio)
|
||||
{
|
||||
name = "RedistributeBeadingStrategy";
|
||||
}
|
||||
|
||||
coord_t RedistributeBeadingStrategy::getOptimalThickness(coord_t bead_count) const
|
||||
{
|
||||
const coord_t inner_bead_count = std::max(static_cast<coord_t>(0), bead_count - 2);
|
||||
const coord_t outer_bead_count = bead_count - inner_bead_count;
|
||||
return parent->getOptimalThickness(inner_bead_count) + optimal_width_outer * outer_bead_count;
|
||||
}
|
||||
|
||||
coord_t RedistributeBeadingStrategy::getTransitionThickness(coord_t lower_bead_count) const
|
||||
{
|
||||
switch (lower_bead_count) {
|
||||
case 0: return minimum_variable_line_ratio * optimal_width_outer;
|
||||
case 1: return (1.0 + parent->getSplitMiddleThreshold()) * optimal_width_outer;
|
||||
default: return parent->getTransitionThickness(lower_bead_count - 2) + 2 * optimal_width_outer;
|
||||
}
|
||||
}
|
||||
|
||||
coord_t RedistributeBeadingStrategy::getOptimalBeadCount(coord_t thickness) const
|
||||
{
|
||||
if (thickness < minimum_variable_line_ratio * optimal_width_outer)
|
||||
return 0;
|
||||
if (thickness <= 2 * optimal_width_outer)
|
||||
return thickness > (1.0 + parent->getSplitMiddleThreshold()) * optimal_width_outer ? 2 : 1;
|
||||
return parent->getOptimalBeadCount(thickness - 2 * optimal_width_outer) + 2;
|
||||
}
|
||||
|
||||
coord_t RedistributeBeadingStrategy::getTransitioningLength(coord_t lower_bead_count) const
|
||||
{
|
||||
return parent->getTransitioningLength(lower_bead_count);
|
||||
}
|
||||
|
||||
float RedistributeBeadingStrategy::getTransitionAnchorPos(coord_t lower_bead_count) const
|
||||
{
|
||||
return parent->getTransitionAnchorPos(lower_bead_count);
|
||||
}
|
||||
|
||||
std::string RedistributeBeadingStrategy::toString() const
|
||||
{
|
||||
return std::string("RedistributeBeadingStrategy+") + parent->toString();
|
||||
}
|
||||
|
||||
BeadingStrategy::Beading RedistributeBeadingStrategy::compute(coord_t thickness, coord_t bead_count) const
|
||||
{
|
||||
Beading ret;
|
||||
|
||||
// Take care of all situations in which no lines are actually produced:
|
||||
if (bead_count == 0 || thickness < minimum_variable_line_ratio * optimal_width_outer) {
|
||||
ret.left_over = thickness;
|
||||
ret.total_thickness = thickness;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Compute the beadings of the inner walls, if any:
|
||||
const coord_t inner_bead_count = bead_count - 2;
|
||||
const coord_t inner_thickness = thickness - 2 * optimal_width_outer;
|
||||
if (inner_bead_count > 0 && inner_thickness > 0) {
|
||||
ret = parent->compute(inner_thickness, inner_bead_count);
|
||||
for (auto &toolpath_location : ret.toolpath_locations) toolpath_location += optimal_width_outer;
|
||||
}
|
||||
|
||||
// Insert the outer wall(s) around the previously computed inner wall(s), which may be empty:
|
||||
const coord_t actual_outer_thickness = bead_count > 2 ? std::min(thickness / 2, optimal_width_outer) : thickness / bead_count;
|
||||
ret.bead_widths.insert(ret.bead_widths.begin(), actual_outer_thickness);
|
||||
ret.toolpath_locations.insert(ret.toolpath_locations.begin(), actual_outer_thickness / 2);
|
||||
if (bead_count > 1) {
|
||||
ret.bead_widths.push_back(actual_outer_thickness);
|
||||
ret.toolpath_locations.push_back(thickness - actual_outer_thickness / 2);
|
||||
}
|
||||
|
||||
// Ensure correct total and left over thickness.
|
||||
ret.total_thickness = thickness;
|
||||
ret.left_over = thickness - std::accumulate(ret.bead_widths.cbegin(), ret.bead_widths.cend(), static_cast<coord_t>(0));
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef REDISTRIBUTE_DISTRIBUTED_BEADING_STRATEGY_H
|
||||
#define REDISTRIBUTE_DISTRIBUTED_BEADING_STRATEGY_H
|
||||
|
||||
#include "BeadingStrategy.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
/*!
|
||||
* A meta-beading-strategy that takes outer and inner wall widths into account.
|
||||
*
|
||||
* The outer wall will try to keep a constant width by only applying the beading strategy on the inner walls. This
|
||||
* ensures that this outer wall doesn't react to changes happening to inner walls. It will limit print artifacts on
|
||||
* the surface of the print. Although this strategy technically deviates from the original philosophy of the paper.
|
||||
* It will generally results in better prints because of a smoother motion and less variation in extrusion width in
|
||||
* the outer walls.
|
||||
*
|
||||
* If the thickness of the model is less then two times the optimal outer wall width and once the minimum inner wall
|
||||
* width it will keep the minimum inner wall at a minimum constant and vary the outer wall widths symmetrical. Until
|
||||
* The thickness of the model is that of at least twice the optimal outer wall width it will then use two
|
||||
* symmetrical outer walls only. Until it transitions into a single outer wall. These last scenario's are always
|
||||
* symmetrical in nature, disregarding the user specified strategy.
|
||||
*/
|
||||
class RedistributeBeadingStrategy : public BeadingStrategy
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* /param optimal_width_outer Outer wall width, guaranteed to be the actual (save rounding errors) at a
|
||||
* bead count if the parent strategies' optimum bead width is a weighted
|
||||
* average of the outer and inner walls at that bead count.
|
||||
* /param minimum_variable_line_ratio Minimum factor that the variable line might deviate from the optimal width.
|
||||
*/
|
||||
RedistributeBeadingStrategy(coord_t optimal_width_outer, double minimum_variable_line_ratio, BeadingStrategyPtr parent);
|
||||
|
||||
~RedistributeBeadingStrategy() override = default;
|
||||
|
||||
Beading compute(coord_t thickness, coord_t bead_count) const override;
|
||||
|
||||
coord_t getOptimalThickness(coord_t bead_count) const override;
|
||||
coord_t getTransitionThickness(coord_t lower_bead_count) const override;
|
||||
coord_t getOptimalBeadCount(coord_t thickness) const override;
|
||||
coord_t getTransitioningLength(coord_t lower_bead_count) const override;
|
||||
float getTransitionAnchorPos(coord_t lower_bead_count) const override;
|
||||
|
||||
std::string toString() const override;
|
||||
|
||||
protected:
|
||||
BeadingStrategyPtr parent;
|
||||
coord_t optimal_width_outer;
|
||||
double minimum_variable_line_ratio;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // INWARD_DISTRIBUTED_BEADING_STRATEGY_H
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "WideningBeadingStrategy.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
WideningBeadingStrategy::WideningBeadingStrategy(BeadingStrategyPtr parent, const coord_t min_input_width, const coord_t min_output_width)
|
||||
: BeadingStrategy(*parent)
|
||||
, parent(std::move(parent))
|
||||
, min_input_width(min_input_width)
|
||||
, min_output_width(min_output_width)
|
||||
{
|
||||
}
|
||||
|
||||
std::string WideningBeadingStrategy::toString() const
|
||||
{
|
||||
return std::string("Widening+") + parent->toString();
|
||||
}
|
||||
|
||||
WideningBeadingStrategy::Beading WideningBeadingStrategy::compute(coord_t thickness, coord_t bead_count) const
|
||||
{
|
||||
if (thickness < optimal_width) {
|
||||
Beading ret;
|
||||
ret.total_thickness = thickness;
|
||||
if (thickness >= min_input_width)
|
||||
{
|
||||
ret.bead_widths.emplace_back(std::max(thickness, min_output_width));
|
||||
ret.toolpath_locations.emplace_back(thickness / 2);
|
||||
} else {
|
||||
ret.left_over = thickness;
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
return parent->compute(thickness, bead_count);
|
||||
}
|
||||
}
|
||||
|
||||
coord_t WideningBeadingStrategy::getOptimalThickness(coord_t bead_count) const
|
||||
{
|
||||
return parent->getOptimalThickness(bead_count);
|
||||
}
|
||||
|
||||
coord_t WideningBeadingStrategy::getTransitionThickness(coord_t lower_bead_count) const
|
||||
{
|
||||
if (lower_bead_count == 0)
|
||||
return min_input_width;
|
||||
else
|
||||
return parent->getTransitionThickness(lower_bead_count);
|
||||
}
|
||||
|
||||
coord_t WideningBeadingStrategy::getOptimalBeadCount(coord_t thickness) const
|
||||
{
|
||||
if (thickness < min_input_width)
|
||||
return 0;
|
||||
coord_t ret = parent->getOptimalBeadCount(thickness);
|
||||
if (thickness >= min_input_width && ret < 1)
|
||||
return 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
coord_t WideningBeadingStrategy::getTransitioningLength(coord_t lower_bead_count) const
|
||||
{
|
||||
return parent->getTransitioningLength(lower_bead_count);
|
||||
}
|
||||
|
||||
float WideningBeadingStrategy::getTransitionAnchorPos(coord_t lower_bead_count) const
|
||||
{
|
||||
return parent->getTransitionAnchorPos(lower_bead_count);
|
||||
}
|
||||
|
||||
std::vector<coord_t> WideningBeadingStrategy::getNonlinearThicknesses(coord_t lower_bead_count) const
|
||||
{
|
||||
std::vector<coord_t> ret;
|
||||
ret.emplace_back(min_output_width);
|
||||
std::vector<coord_t> pret = parent->getNonlinearThicknesses(lower_bead_count);
|
||||
ret.insert(ret.end(), pret.begin(), pret.end());
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef WIDENING_BEADING_STRATEGY_H
|
||||
#define WIDENING_BEADING_STRATEGY_H
|
||||
|
||||
#include "BeadingStrategy.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
/*!
|
||||
* This is a meta-strategy that can be applied on any other beading strategy. If
|
||||
* the part is thinner than a single line, this strategy adjusts the part so
|
||||
* that it becomes the minimum thickness of one line.
|
||||
*
|
||||
* This way, tiny pieces that are smaller than a single line will still be
|
||||
* printed.
|
||||
*/
|
||||
class WideningBeadingStrategy : public BeadingStrategy
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* Takes responsibility for deleting \param parent
|
||||
*/
|
||||
WideningBeadingStrategy(BeadingStrategyPtr parent, coord_t min_input_width, coord_t min_output_width);
|
||||
|
||||
~WideningBeadingStrategy() override = default;
|
||||
|
||||
Beading compute(coord_t thickness, coord_t bead_count) const override;
|
||||
coord_t getOptimalThickness(coord_t bead_count) const override;
|
||||
coord_t getTransitionThickness(coord_t lower_bead_count) const override;
|
||||
coord_t getOptimalBeadCount(coord_t thickness) const override;
|
||||
coord_t getTransitioningLength(coord_t lower_bead_count) const override;
|
||||
float getTransitionAnchorPos(coord_t lower_bead_count) const override;
|
||||
std::vector<coord_t> getNonlinearThicknesses(coord_t lower_bead_count) const override;
|
||||
std::string toString() const override;
|
||||
|
||||
protected:
|
||||
BeadingStrategyPtr parent;
|
||||
const coord_t min_input_width;
|
||||
const coord_t min_output_width;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // WIDENING_BEADING_STRATEGY_H
|
||||
2140
src/libslic3r/Arachne/SkeletalTrapezoidation.cpp
Normal file
2140
src/libslic3r/Arachne/SkeletalTrapezoidation.cpp
Normal file
File diff suppressed because it is too large
Load diff
595
src/libslic3r/Arachne/SkeletalTrapezoidation.hpp
Normal file
595
src/libslic3r/Arachne/SkeletalTrapezoidation.hpp
Normal file
|
|
@ -0,0 +1,595 @@
|
|||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef SKELETAL_TRAPEZOIDATION_H
|
||||
#define SKELETAL_TRAPEZOIDATION_H
|
||||
|
||||
#include <boost/polygon/voronoi.hpp>
|
||||
|
||||
#include <memory> // smart pointers
|
||||
#include <unordered_map>
|
||||
#include <utility> // pair
|
||||
|
||||
#include "utils/HalfEdgeGraph.hpp"
|
||||
#include "utils/PolygonsSegmentIndex.hpp"
|
||||
#include "utils/ExtrusionJunction.hpp"
|
||||
#include "utils/ExtrusionLine.hpp"
|
||||
#include "SkeletalTrapezoidationEdge.hpp"
|
||||
#include "SkeletalTrapezoidationJoint.hpp"
|
||||
#include "libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp"
|
||||
#include "SkeletalTrapezoidationGraph.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
/*!
|
||||
* Main class of the dynamic beading strategies.
|
||||
*
|
||||
* The input polygon region is decomposed into trapezoids and represented as a half-edge data-structure.
|
||||
*
|
||||
* We determine which edges are 'central' accordinding to the transitioning_angle of the beading strategy,
|
||||
* and determine the bead count for these central regions and apply them outward when generating toolpaths. [oversimplified]
|
||||
*
|
||||
* The method can be visually explained as generating the 3D union of cones surface on the outline polygons,
|
||||
* and changing the heights along central regions of that surface so that they are flat.
|
||||
* For more info, please consult the paper "A framework for adaptive width control of dense contour-parallel toolpaths in fused
|
||||
deposition modeling" by Kuipers et al.
|
||||
* This visual explanation aid explains the use of "upward", "lower" etc,
|
||||
* i.e. the radial distance and/or the bead count are used as heights of this visualization, there is no coordinate called 'Z'.
|
||||
*
|
||||
* TODO: split this class into two:
|
||||
* 1. Class for generating the decomposition and aux functions for performing updates
|
||||
* 2. Class for editing the structure for our purposes.
|
||||
*/
|
||||
class SkeletalTrapezoidation
|
||||
{
|
||||
using pos_t = double;
|
||||
using vd_t = boost::polygon::voronoi_diagram<pos_t>;
|
||||
using graph_t = SkeletalTrapezoidationGraph;
|
||||
using edge_t = STHalfEdge;
|
||||
using node_t = STHalfEdgeNode;
|
||||
using Beading = BeadingStrategy::Beading;
|
||||
using BeadingPropagation = SkeletalTrapezoidationJoint::BeadingPropagation;
|
||||
using TransitionMiddle = SkeletalTrapezoidationEdge::TransitionMiddle;
|
||||
using TransitionEnd = SkeletalTrapezoidationEdge::TransitionEnd;
|
||||
|
||||
template<typename T>
|
||||
using ptr_vector_t = std::vector<std::shared_ptr<T>>;
|
||||
|
||||
double transitioning_angle; //!< How pointy a region should be before we apply the method. Equals 180* - limit_bisector_angle
|
||||
coord_t discretization_step_size; //!< approximate size of segments when parabolic VD edges get discretized (and vertex-vertex edges)
|
||||
coord_t transition_filter_dist; //!< Filter transition mids (i.e. anchors) closer together than this
|
||||
coord_t allowed_filter_deviation; //!< The allowed line width deviation induced by filtering
|
||||
coord_t beading_propagation_transition_dist; //!< When there are different beadings propagated from below and from above, use this transitioning distance
|
||||
static constexpr coord_t central_filter_dist = scaled<coord_t>(0.02); //!< Filter areas marked as 'central' smaller than this
|
||||
static constexpr coord_t snap_dist = scaled<coord_t>(0.02); //!< Generic arithmatic inaccuracy. Only used to determine whether a transition really needs to insert an extra edge.
|
||||
|
||||
/*!
|
||||
* The strategy to use to fill a certain shape with lines.
|
||||
*
|
||||
* Various BeadingStrategies are available that differ in which lines get to
|
||||
* print at their optimal width, where the play is being compensated, and
|
||||
* how the joints are handled where we transition to different numbers of
|
||||
* lines.
|
||||
*/
|
||||
const BeadingStrategy& beading_strategy;
|
||||
|
||||
public:
|
||||
using Segment = PolygonsSegmentIndex;
|
||||
|
||||
/*!
|
||||
* Construct a new trapezoidation problem to solve.
|
||||
* \param polys The shapes to fill with walls.
|
||||
* \param beading_strategy The strategy to use to fill these shapes.
|
||||
* \param transitioning_angle Where we transition to a different number of
|
||||
* walls, how steep should this transition be? A lower angle means that the
|
||||
* transition will be longer.
|
||||
* \param discretization_step_size Since g-code can't represent smooth
|
||||
* transitions in line width, the line width must change with discretized
|
||||
* steps. This indicates how long the line segments between those steps will
|
||||
* be.
|
||||
* \param transition_filter_dist The minimum length of transitions.
|
||||
* Transitions shorter than this will be considered for dissolution.
|
||||
* \param beading_propagation_transition_dist When there are different
|
||||
* beadings propagated from below and from above, use this transitioning
|
||||
* distance.
|
||||
*/
|
||||
SkeletalTrapezoidation(const Polygons& polys,
|
||||
const BeadingStrategy& beading_strategy,
|
||||
double transitioning_angle
|
||||
, coord_t discretization_step_size
|
||||
, coord_t transition_filter_dist
|
||||
, coord_t allowed_filter_deviation
|
||||
, coord_t beading_propagation_transition_dist);
|
||||
|
||||
/*!
|
||||
* A skeletal graph through the polygons that we need to fill with beads.
|
||||
*
|
||||
* The skeletal graph represents the medial axes through each part of the
|
||||
* polygons, and the lines from these medial axes towards each vertex of the
|
||||
* polygons. The graph can be used to see what the width is of a polygon in
|
||||
* each place and where the width transitions.
|
||||
*/
|
||||
graph_t graph;
|
||||
|
||||
/*!
|
||||
* Generate the paths that the printer must extrude, to print the outlines
|
||||
* in the input polygons.
|
||||
* \param filter_outermost_central_edges Some edges are "central" but still
|
||||
* touch the outside of the polygon. If enabled, don't treat these as
|
||||
* "central" but as if it's a obtuse corner. As a result, sharp corners will
|
||||
* no longer end in a single line but will just loop.
|
||||
*/
|
||||
void generateToolpaths(std::vector<VariableWidthLines> &generated_toolpaths, bool filter_outermost_central_edges = false);
|
||||
|
||||
protected:
|
||||
/*!
|
||||
* Auxiliary for referencing one transition along an edge which may contain multiple transitions
|
||||
*/
|
||||
struct TransitionMidRef
|
||||
{
|
||||
edge_t* edge;
|
||||
std::list<TransitionMiddle>::iterator transition_it;
|
||||
TransitionMidRef(edge_t* edge, std::list<TransitionMiddle>::iterator transition_it)
|
||||
: edge(edge)
|
||||
, transition_it(transition_it)
|
||||
{}
|
||||
};
|
||||
|
||||
/*!
|
||||
* Compute the skeletal trapezoidation decomposition of the input shape.
|
||||
*
|
||||
* Compute the Voronoi Diagram (VD) and transfer all inside edges into our half-edge (HE) datastructure.
|
||||
*
|
||||
* The algorithm is currently a bit overcomplicated, because the discretization of parabolic edges is performed at the same time as all edges are being transfered,
|
||||
* which means that there is no one-to-one mapping from VD edges to HE edges.
|
||||
* Instead we map from a VD edge to the last HE edge.
|
||||
* This could be cimplified by recording the edges which should be discretized and discretizing the mafterwards.
|
||||
*
|
||||
* Another complication arises because the VD uses floating logic, which can result in zero-length segments after rounding to integers.
|
||||
* We therefore collapse edges and their whole cells afterwards.
|
||||
*/
|
||||
void constructFromPolygons(const Polygons& polys);
|
||||
|
||||
/*!
|
||||
* mapping each voronoi VD edge to the corresponding halfedge HE edge
|
||||
* In case the result segment is discretized, we map the VD edge to the *last* HE edge
|
||||
*/
|
||||
std::unordered_map<vd_t::edge_type*, edge_t*> vd_edge_to_he_edge;
|
||||
std::unordered_map<vd_t::vertex_type*, node_t*> vd_node_to_he_node;
|
||||
node_t& makeNode(vd_t::vertex_type& vd_node, Point p); //!< Get the node which the VD node maps to, or create a new mapping if there wasn't any yet.
|
||||
|
||||
/*!
|
||||
* (Eventual) returned 'polylines per index' result (from generateToolpaths):
|
||||
*/
|
||||
std::vector<VariableWidthLines> *p_generated_toolpaths;
|
||||
|
||||
/*!
|
||||
* Transfer an edge from the VD to the HE and perform discretization of parabolic edges (and vertex-vertex edges)
|
||||
* \p prev_edge serves as input and output. May be null as input.
|
||||
*/
|
||||
void transferEdge(Point from, Point to, vd_t::edge_type& vd_edge, edge_t*& prev_edge, Point& start_source_point, Point& end_source_point, const std::vector<Segment>& segments);
|
||||
|
||||
/*!
|
||||
* Discretize a Voronoi edge that represents the medial axis of a vertex-
|
||||
* line region or vertex-vertex region into small segments that can be
|
||||
* considered to have a straight medial axis and a linear line width
|
||||
* transition.
|
||||
*
|
||||
* The medial axis between a point and a line is a parabola. The rest of the
|
||||
* algorithm doesn't want to have to deal with parabola, so this discretises
|
||||
* the parabola into straight line segments. This is necessary if there is a
|
||||
* sharp inner corner (acts as a point) that comes close to a straight edge.
|
||||
*
|
||||
* The medial axis between a point and a point is a straight line segment.
|
||||
* However the distance from the medial axis to either of those points draws
|
||||
* a parabola as you go along the medial axis. That means that the resulting
|
||||
* line width along the medial axis would not be linearly increasing or
|
||||
* linearly decreasing, but needs to take the shape of a parabola. Instead,
|
||||
* we'll break this edge up into tiny line segments that can approximate the
|
||||
* parabola with tiny linear increases or decreases in line width.
|
||||
* \param segment The variable-width Voronoi edge to discretize.
|
||||
* \param points All vertices of the original Polygons to fill with beads.
|
||||
* \param segments All line segments of the original Polygons to fill with
|
||||
* beads.
|
||||
* \return A number of coordinates along the edge where the edge is broken
|
||||
* up into discrete pieces.
|
||||
*/
|
||||
std::vector<Point> discretize(const vd_t::edge_type& segment, const std::vector<Segment>& segments);
|
||||
|
||||
/*!
|
||||
* Compute the range of line segments that surround a cell of the skeletal
|
||||
* graph that belongs to a point on the medial axis.
|
||||
*
|
||||
* This should only be used on cells that belong to a corner in the skeletal
|
||||
* graph, e.g. triangular cells, not trapezoid cells.
|
||||
*
|
||||
* The resulting line segments is just the first and the last segment. They
|
||||
* are linked to the neighboring segments, so you can iterate over the
|
||||
* segments until you reach the last segment.
|
||||
* \param cell The cell to compute the range of line segments for.
|
||||
* \param[out] start_source_point The start point of the source segment of
|
||||
* this cell.
|
||||
* \param[out] end_source_point The end point of the source segment of this
|
||||
* cell.
|
||||
* \param[out] starting_vd_edge The edge of the Voronoi diagram where the
|
||||
* loop around the cell starts.
|
||||
* \param[out] ending_vd_edge The edge of the Voronoi diagram where the loop
|
||||
* around the cell ends.
|
||||
* \param points All vertices of the input Polygons.
|
||||
* \param segments All edges of the input Polygons.
|
||||
* /return Whether the cell is inside of the polygon. If it's outside of the
|
||||
* polygon we should skip processing it altogether.
|
||||
*/
|
||||
bool computePointCellRange(vd_t::cell_type& cell, Point& start_source_point, Point& end_source_point, vd_t::edge_type*& starting_vd_edge, vd_t::edge_type*& ending_vd_edge, const std::vector<Segment>& segments);
|
||||
|
||||
/*!
|
||||
* Compute the range of line segments that surround a cell of the skeletal
|
||||
* graph that belongs to a line segment of the medial axis.
|
||||
*
|
||||
* This should only be used on cells that belong to a central line segment
|
||||
* of the skeletal graph, e.g. trapezoid cells, not triangular cells.
|
||||
*
|
||||
* The resulting line segments is just the first and the last segment. They
|
||||
* are linked to the neighboring segments, so you can iterate over the
|
||||
* segments until you reach the last segment.
|
||||
* \param cell The cell to compute the range of line segments for.
|
||||
* \param[out] start_source_point The start point of the source segment of
|
||||
* this cell.
|
||||
* \param[out] end_source_point The end point of the source segment of this
|
||||
* cell.
|
||||
* \param[out] starting_vd_edge The edge of the Voronoi diagram where the
|
||||
* loop around the cell starts.
|
||||
* \param[out] ending_vd_edge The edge of the Voronoi diagram where the loop
|
||||
* around the cell ends.
|
||||
* \param points All vertices of the input Polygons.
|
||||
* \param segments All edges of the input Polygons.
|
||||
* /return Whether the cell is inside of the polygon. If it's outside of the
|
||||
* polygon we should skip processing it altogether.
|
||||
*/
|
||||
void computeSegmentCellRange(vd_t::cell_type& cell, Point& start_source_point, Point& end_source_point, vd_t::edge_type*& starting_vd_edge, vd_t::edge_type*& ending_vd_edge, const std::vector<Segment>& segments);
|
||||
|
||||
/*!
|
||||
* For VD cells associated with an input polygon vertex, we need to separate the node at the end and start of the cell into two
|
||||
* That way we can reach both the quad_start and the quad_end from the [incident_edge] of the two new nodes
|
||||
* Otherwise if node.incident_edge = quad_start you couldnt reach quad_end.twin by normal iteration (i.e. it = it.twin.next)
|
||||
*/
|
||||
void separatePointyQuadEndNodes();
|
||||
|
||||
|
||||
// ^ init | v transitioning
|
||||
|
||||
void updateIsCentral(); // Update the "is_central" flag for each edge based on the transitioning_angle
|
||||
|
||||
/*!
|
||||
* Filter out small central areas.
|
||||
*
|
||||
* Only used to get rid of small edges which get marked as central because
|
||||
* of rounding errors because the region is so small.
|
||||
*/
|
||||
void filterCentral(coord_t max_length);
|
||||
|
||||
/*!
|
||||
* Filter central areas connected to starting_edge recursively.
|
||||
* \return Whether we should unmark this section marked as central, on the
|
||||
* way back out of the recursion.
|
||||
*/
|
||||
bool filterCentral(edge_t* starting_edge, coord_t traveled_dist, coord_t max_length);
|
||||
|
||||
/*!
|
||||
* Unmark the outermost edges directly connected to the outline, as not
|
||||
* being central.
|
||||
*
|
||||
* Only used to emulate some related literature.
|
||||
*
|
||||
* The paper shows that this function is bad for the stability of the framework.
|
||||
*/
|
||||
void filterOuterCentral();
|
||||
|
||||
/*!
|
||||
* Set bead count in central regions based on the optimal_bead_count of the
|
||||
* beading strategy.
|
||||
*/
|
||||
void updateBeadCount();
|
||||
|
||||
/*!
|
||||
* Add central regions and set bead counts where there is an end of the
|
||||
* central area and when traveling upward we get to another region with the
|
||||
* same bead count.
|
||||
*/
|
||||
void filterNoncentralRegions();
|
||||
|
||||
/*!
|
||||
* Add central regions and set bead counts for a particular edge and all of
|
||||
* its adjacent edges.
|
||||
*
|
||||
* Recursive subroutine for \ref filterNoncentralRegions().
|
||||
* \return Whether to set the bead count on the way back
|
||||
*/
|
||||
bool filterNoncentralRegions(edge_t* to_edge, coord_t bead_count, coord_t traveled_dist, coord_t max_dist);
|
||||
|
||||
/*!
|
||||
* Generate middle points of all transitions on edges.
|
||||
*
|
||||
* The transition middle points are saved in the graph itself. They are also
|
||||
* returned via the output parameter.
|
||||
* \param[out] edge_transitions A list of transitions that were generated.
|
||||
*/
|
||||
void generateTransitionMids(ptr_vector_t<std::list<TransitionMiddle>>& edge_transitions);
|
||||
|
||||
/*!
|
||||
* Removes some transition middle points.
|
||||
*
|
||||
* Transitions can be removed if there are multiple intersecting transitions
|
||||
* that are too close together. If transitions have opposite effects, both
|
||||
* are removed.
|
||||
*/
|
||||
void filterTransitionMids();
|
||||
|
||||
/*!
|
||||
* Merge transitions that are too close together.
|
||||
* \param edge_to_start Edge pointing to the node from which to start
|
||||
* traveling in all directions except along \p edge_to_start .
|
||||
* \param origin_transition The transition for which we are checking nearby
|
||||
* transitions.
|
||||
* \param traveled_dist The distance traveled before we came to
|
||||
* \p edge_to_start.to .
|
||||
* \param going_up Whether we are traveling in the upward direction as seen
|
||||
* from the \p origin_transition. If this doesn't align with the direction
|
||||
* according to the R diff on a consecutive edge we know there was a local
|
||||
* optimum.
|
||||
* \return Whether the origin transition should be dissolved.
|
||||
*/
|
||||
std::list<TransitionMidRef> dissolveNearbyTransitions(edge_t* edge_to_start, TransitionMiddle& origin_transition, coord_t traveled_dist, coord_t max_dist, bool going_up);
|
||||
|
||||
/*!
|
||||
* Spread a certain bead count over a region in the graph.
|
||||
* \param edge_to_start One edge of the region to spread the bead count in.
|
||||
* \param from_bead_count All edges with this bead count will be changed.
|
||||
* \param to_bead_count The new bead count for those edges.
|
||||
*/
|
||||
void dissolveBeadCountRegion(edge_t* edge_to_start, coord_t from_bead_count, coord_t to_bead_count);
|
||||
|
||||
/*!
|
||||
* Change the bead count if the given edge is at the end of a central
|
||||
* region.
|
||||
*
|
||||
* This is necessary to provide a transitioning bead count to the edges of a
|
||||
* central region to transition more smoothly from a high bead count in the
|
||||
* central region to a lower bead count at the edge.
|
||||
* \param edge_to_start One edge from a zone that needs to be filtered.
|
||||
* \param traveled_dist The distance along the edges we've traveled so far.
|
||||
* \param max_distance Don't filter beyond this range.
|
||||
* \param replacing_bead_count The new bead count for this region.
|
||||
* \return ``true`` if the bead count of this edge was changed.
|
||||
*/
|
||||
bool filterEndOfCentralTransition(edge_t* edge_to_start, coord_t traveled_dist, coord_t max_dist, coord_t replacing_bead_count);
|
||||
|
||||
/*!
|
||||
* Generate the endpoints of all transitions for all edges in the graph.
|
||||
* \param[out] edge_transition_ends The resulting transition endpoints.
|
||||
*/
|
||||
void generateAllTransitionEnds(ptr_vector_t<std::list<TransitionEnd>>& edge_transition_ends);
|
||||
|
||||
/*!
|
||||
* Also set the rest values at nodes in between the transition ends
|
||||
*/
|
||||
void applyTransitions(ptr_vector_t<std::list<TransitionEnd>>& edge_transition_ends);
|
||||
|
||||
/*!
|
||||
* Create extra edges along all edges, where it needs to transition from one
|
||||
* bead count to another.
|
||||
*
|
||||
* For example, if an edge of the graph goes from a bead count of 6 to a
|
||||
* bead count of 1, it needs to generate 5 places where the beads around
|
||||
* this line transition to a lower bead count. These are the "ribs". They
|
||||
* reach from the edge to the border of the polygon. Where the beads hit
|
||||
* those ribs the beads know to make a transition.
|
||||
*/
|
||||
void generateTransitioningRibs();
|
||||
|
||||
/*!
|
||||
* Generate the endpoints of a specific transition midpoint.
|
||||
* \param edge The edge to create transitions on.
|
||||
* \param mid_R The radius of the transition middle point.
|
||||
* \param transition_lower_bead_count The bead count at the lower end of the
|
||||
* transition.
|
||||
* \param[out] edge_transition_ends A list of endpoints to add the new
|
||||
* endpoints to.
|
||||
*/
|
||||
void generateTransitionEnds(edge_t& edge, coord_t mid_R, coord_t transition_lower_bead_count, ptr_vector_t<std::list<TransitionEnd>>& edge_transition_ends);
|
||||
|
||||
/*!
|
||||
* Compute a single endpoint of a transition.
|
||||
* \param edge The edge to generate the endpoint for.
|
||||
* \param start_pos The position where the transition starts.
|
||||
* \param end_pos The position where the transition ends on the other side.
|
||||
* \param transition_half_length The distance to the transition middle
|
||||
* point.
|
||||
* \param start_rest The gap between the start of the transition and the
|
||||
* starting endpoint, as ratio of the inner bead width at the high end of
|
||||
* the transition.
|
||||
* \param end_rest The gap between the end of the transition and the ending
|
||||
* endpoint, as ratio of the inner bead width at the high end of the
|
||||
* transition.
|
||||
* \param transition_lower_bead_count The bead count at the lower end of the
|
||||
* transition.
|
||||
* \param[out] edge_transition_ends The list to put the resulting endpoints
|
||||
* in.
|
||||
* \return Whether the given edge is going downward (i.e. towards a thinner
|
||||
* region of the polygon).
|
||||
*/
|
||||
bool generateTransitionEnd(edge_t& edge, coord_t start_pos, coord_t end_pos, coord_t transition_half_length, double start_rest, double end_rest, coord_t transition_lower_bead_count, ptr_vector_t<std::list<TransitionEnd>>& edge_transition_ends);
|
||||
|
||||
/*!
|
||||
* Determines whether an edge is going downwards or upwards in the graph.
|
||||
*
|
||||
* An edge is said to go "downwards" if it's going towards a narrower part
|
||||
* of the polygon. The notion of "downwards" comes from the conical
|
||||
* representation of the graph, where the polygon is filled with a cone of
|
||||
* maximum radius.
|
||||
*
|
||||
* This function works by recursively checking adjacent edges until the edge
|
||||
* is reached.
|
||||
* \param outgoing The edge to check.
|
||||
* \param traveled_dist The distance traversed so far.
|
||||
* \param transition_half_length The radius of the transition width.
|
||||
* \param lower_bead_count The bead count at the lower end of the edge.
|
||||
* \return ``true`` if this edge is going down, or ``false`` if it's going
|
||||
* up.
|
||||
*/
|
||||
bool isGoingDown(edge_t* outgoing, coord_t traveled_dist, coord_t transition_half_length, coord_t lower_bead_count) const;
|
||||
|
||||
/*!
|
||||
* Determines whether this edge marks the end of the central region.
|
||||
* \param edge The edge to check.
|
||||
* \return ``true`` if this edge goes from a central region to a non-central
|
||||
* region, or ``false`` in every other case (central to central, non-central
|
||||
* to non-central, non-central to central, or end-of-the-line).
|
||||
*/
|
||||
bool isEndOfCentral(const edge_t& edge) const;
|
||||
|
||||
/*!
|
||||
* Create extra ribs in the graph where the graph contains a parabolic arc
|
||||
* or a straight between two inner corners.
|
||||
*
|
||||
* There might be transitions there as the beads go through a narrow
|
||||
* bottleneck in the polygon.
|
||||
*/
|
||||
void generateExtraRibs();
|
||||
|
||||
// ^ transitioning ^
|
||||
|
||||
// v toolpath generation v
|
||||
|
||||
/*!
|
||||
* \param[out] segments the generated segments
|
||||
*/
|
||||
void generateSegments();
|
||||
|
||||
/*!
|
||||
* From a quad (a group of linked edges in one cell of the Voronoi), find
|
||||
* the edge pointing to the node that is furthest away from the border of the polygon.
|
||||
* \param quad_start_edge The first edge of the quad.
|
||||
* \return The edge of the quad that is furthest away from the border.
|
||||
*/
|
||||
edge_t* getQuadMaxRedgeTo(edge_t* quad_start_edge);
|
||||
|
||||
/*!
|
||||
* Propagate beading information from nodes that are closer to the edge
|
||||
* (low radius R) to nodes that are farther from the edge (high R).
|
||||
*
|
||||
* only propagate from nodes with beading info upward to nodes without beading info
|
||||
*
|
||||
* Edges are sorted by their radius, so that we can do a depth-first walk
|
||||
* without employing a recursive algorithm.
|
||||
*
|
||||
* In upward propagated beadings we store the distance traveled, so that we can merge these beadings with the downward propagated beadings in \ref propagateBeadingsDownward(.)
|
||||
*
|
||||
* \param upward_quad_mids all upward halfedges of the inner skeletal edges (not directly connected to the outline) sorted on their highest [distance_to_boundary]. Higher dist first.
|
||||
*/
|
||||
void propagateBeadingsUpward(std::vector<edge_t*>& upward_quad_mids, ptr_vector_t<BeadingPropagation>& node_beadings);
|
||||
|
||||
/*!
|
||||
* propagate beading info from higher R nodes to lower R nodes
|
||||
*
|
||||
* merge with upward propagated beadings if they are encountered
|
||||
*
|
||||
* don't transfer to nodes which lie on the outline polygon
|
||||
*
|
||||
* edges are sorted so that we can do a depth-first walk without employing a recursive algorithm
|
||||
*
|
||||
* \param upward_quad_mids all upward halfedges of the inner skeletal edges (not directly connected to the outline) sorted on their highest [distance_to_boundary]. Higher dist first.
|
||||
*/
|
||||
void propagateBeadingsDownward(std::vector<edge_t*>& upward_quad_mids, ptr_vector_t<BeadingPropagation>& node_beadings);
|
||||
|
||||
/*!
|
||||
* Subroutine of \ref propagateBeadingsDownward(std::vector<edge_t*>&, ptr_vector_t<BeadingPropagation>&)
|
||||
*/
|
||||
void propagateBeadingsDownward(edge_t* edge_to_peak, ptr_vector_t<BeadingPropagation>& node_beadings);
|
||||
|
||||
/*!
|
||||
* Find a beading in between two other beadings.
|
||||
*
|
||||
* This creates a new beading. With this we can find the coordinates of the
|
||||
* endpoints of the actual line segments to draw.
|
||||
*
|
||||
* The parameters \p left and \p right are not actually always left or right
|
||||
* but just arbitrary directions to visually indicate the difference.
|
||||
* \param left One of the beadings to interpolate between.
|
||||
* \param ratio_left_to_whole The position within the two beadings to sample
|
||||
* an interpolation. Should be a ratio between 0 and 1.
|
||||
* \param right One of the beadings to interpolate between.
|
||||
* \param switching_radius The bead radius at which we switch from the left
|
||||
* beading to the merged beading, if the beadings have a different number of
|
||||
* beads.
|
||||
* \return The beading at the interpolated location.
|
||||
*/
|
||||
Beading interpolate(const Beading& left, double ratio_left_to_whole, const Beading& right, coord_t switching_radius) const;
|
||||
|
||||
/*!
|
||||
* Subroutine of \ref interpolate(const Beading&, Ratio, const Beading&, coord_t)
|
||||
*
|
||||
* This creates a new Beading between two beadings, assuming that both have
|
||||
* the same number of beads.
|
||||
* \param left One of the beadings to interpolate between.
|
||||
* \param ratio_left_to_whole The position within the two beadings to sample
|
||||
* an interpolation. Should be a ratio between 0 and 1.
|
||||
* \param right One of the beadings to interpolate between.
|
||||
* \return The beading at the interpolated location.
|
||||
*/
|
||||
Beading interpolate(const Beading& left, double ratio_left_to_whole, const Beading& right) const;
|
||||
|
||||
/*!
|
||||
* Get the beading at a certain node of the skeletal graph, or create one if
|
||||
* it doesn't have one yet.
|
||||
*
|
||||
* This is a lazy get.
|
||||
* \param node The node to get the beading from.
|
||||
* \param node_beadings A list of all beadings for nodes.
|
||||
* \return The beading of that node.
|
||||
*/
|
||||
std::shared_ptr<BeadingPropagation> getOrCreateBeading(node_t* node, ptr_vector_t<BeadingPropagation>& node_beadings);
|
||||
|
||||
/*!
|
||||
* In case we cannot find the beading of a node, get a beading from the
|
||||
* nearest node.
|
||||
* \param node The node to attempt to get a beading from. The actual node
|
||||
* that the returned beading is from may be a different, nearby node.
|
||||
* \param max_dist The maximum distance to search for.
|
||||
* \return A beading for the node, or ``nullptr`` if there is no node nearby
|
||||
* with a beading.
|
||||
*/
|
||||
std::shared_ptr<BeadingPropagation> getNearestBeading(node_t* node, coord_t max_dist);
|
||||
|
||||
/*!
|
||||
* generate junctions for each bone
|
||||
* \param edge_to_junctions junctions ordered high R to low R
|
||||
*/
|
||||
void generateJunctions(ptr_vector_t<BeadingPropagation>& node_beadings, ptr_vector_t<LineJunctions>& edge_junctions);
|
||||
|
||||
/*!
|
||||
* Add a new toolpath segment, defined between two extrusion-juntions.
|
||||
*
|
||||
* \param from The junction from which to add a segment.
|
||||
* \param to The junction to which to add a segment.
|
||||
* \param is_odd Whether this segment is an odd gap filler along the middle of the skeleton.
|
||||
* \param force_new_path Whether to prevent adding this path to an existing path which ends in \p from
|
||||
* \param from_is_3way Whether the \p from junction is a splitting junction where two normal wall lines and a gap filler line come together.
|
||||
* \param to_is_3way Whether the \p to junction is a splitting junction where two normal wall lines and a gap filler line come together.
|
||||
*/
|
||||
void addToolpathSegment(const ExtrusionJunction& from, const ExtrusionJunction& to, bool is_odd, bool force_new_path, bool from_is_3way, bool to_is_3way);
|
||||
|
||||
/*!
|
||||
* connect junctions in each quad
|
||||
*/
|
||||
void connectJunctions(ptr_vector_t<LineJunctions>& edge_junctions);
|
||||
|
||||
/*!
|
||||
* Genrate small segments for local maxima where the beading would only result in a single bead
|
||||
*/
|
||||
void generateLocalMaximaSingleBeads();
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // VORONOI_QUADRILATERALIZATION_H
|
||||
122
src/libslic3r/Arachne/SkeletalTrapezoidationEdge.hpp
Normal file
122
src/libslic3r/Arachne/SkeletalTrapezoidationEdge.hpp
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
//Copyright (c) 2021 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef SKELETAL_TRAPEZOIDATION_EDGE_H
|
||||
#define SKELETAL_TRAPEZOIDATION_EDGE_H
|
||||
|
||||
#include <memory> // smart pointers
|
||||
#include <list>
|
||||
#include <vector>
|
||||
|
||||
#include "utils/ExtrusionJunction.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
class SkeletalTrapezoidationEdge
|
||||
{
|
||||
private:
|
||||
enum class Central { UNKNOWN = -1, NO, YES };
|
||||
|
||||
public:
|
||||
/*!
|
||||
* Representing the location along an edge where the anchor position of a transition should be placed.
|
||||
*/
|
||||
struct TransitionMiddle
|
||||
{
|
||||
coord_t pos; // Position along edge as measure from edge.from.p
|
||||
int lower_bead_count;
|
||||
coord_t feature_radius; // The feature radius at which this transition is placed
|
||||
TransitionMiddle(coord_t pos, int lower_bead_count, coord_t feature_radius)
|
||||
: pos(pos), lower_bead_count(lower_bead_count)
|
||||
, feature_radius(feature_radius)
|
||||
{}
|
||||
};
|
||||
|
||||
/*!
|
||||
* Represents the location along an edge where the lower or upper end of a transition should be placed.
|
||||
*/
|
||||
struct TransitionEnd
|
||||
{
|
||||
coord_t pos; // Position along edge as measure from edge.from.p, where the edge is always the half edge oriented from lower to higher R
|
||||
int lower_bead_count;
|
||||
bool is_lower_end; // Whether this is the ed of the transition with lower bead count
|
||||
TransitionEnd(coord_t pos, int lower_bead_count, bool is_lower_end)
|
||||
: pos(pos), lower_bead_count(lower_bead_count), is_lower_end(is_lower_end)
|
||||
{}
|
||||
};
|
||||
|
||||
enum class EdgeType
|
||||
{
|
||||
NORMAL = 0, // from voronoi diagram
|
||||
EXTRA_VD = 1, // introduced to voronoi diagram in order to make the gMAT
|
||||
TRANSITION_END = 2 // introduced to voronoi diagram in order to make the gMAT
|
||||
};
|
||||
EdgeType type;
|
||||
|
||||
SkeletalTrapezoidationEdge() : SkeletalTrapezoidationEdge(EdgeType::NORMAL) {}
|
||||
SkeletalTrapezoidationEdge(const EdgeType &type) : type(type), is_central(Central::UNKNOWN) {}
|
||||
|
||||
bool isCentral() const
|
||||
{
|
||||
assert(is_central != Central::UNKNOWN);
|
||||
return is_central == Central::YES;
|
||||
}
|
||||
void setIsCentral(bool b)
|
||||
{
|
||||
is_central = b ? Central::YES : Central::NO;
|
||||
}
|
||||
bool centralIsSet() const
|
||||
{
|
||||
return is_central != Central::UNKNOWN;
|
||||
}
|
||||
|
||||
bool hasTransitions(bool ignore_empty = false) const
|
||||
{
|
||||
return transitions.use_count() > 0 && (ignore_empty || ! transitions.lock()->empty());
|
||||
}
|
||||
void setTransitions(std::shared_ptr<std::list<TransitionMiddle>> storage)
|
||||
{
|
||||
transitions = storage;
|
||||
}
|
||||
std::shared_ptr<std::list<TransitionMiddle>> getTransitions()
|
||||
{
|
||||
return transitions.lock();
|
||||
}
|
||||
|
||||
bool hasTransitionEnds(bool ignore_empty = false) const
|
||||
{
|
||||
return transition_ends.use_count() > 0 && (ignore_empty || ! transition_ends.lock()->empty());
|
||||
}
|
||||
void setTransitionEnds(std::shared_ptr<std::list<TransitionEnd>> storage)
|
||||
{
|
||||
transition_ends = storage;
|
||||
}
|
||||
std::shared_ptr<std::list<TransitionEnd>> getTransitionEnds()
|
||||
{
|
||||
return transition_ends.lock();
|
||||
}
|
||||
|
||||
bool hasExtrusionJunctions(bool ignore_empty = false) const
|
||||
{
|
||||
return extrusion_junctions.use_count() > 0 && (ignore_empty || ! extrusion_junctions.lock()->empty());
|
||||
}
|
||||
void setExtrusionJunctions(std::shared_ptr<LineJunctions> storage)
|
||||
{
|
||||
extrusion_junctions = storage;
|
||||
}
|
||||
std::shared_ptr<LineJunctions> getExtrusionJunctions()
|
||||
{
|
||||
return extrusion_junctions.lock();
|
||||
}
|
||||
|
||||
private:
|
||||
Central is_central; //! whether the edge is significant; whether the source segments have a sharp angle; -1 is unknown
|
||||
|
||||
std::weak_ptr<std::list<TransitionMiddle>> transitions;
|
||||
std::weak_ptr<std::list<TransitionEnd>> transition_ends;
|
||||
std::weak_ptr<LineJunctions> extrusion_junctions;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // SKELETAL_TRAPEZOIDATION_EDGE_H
|
||||
467
src/libslic3r/Arachne/SkeletalTrapezoidationGraph.cpp
Normal file
467
src/libslic3r/Arachne/SkeletalTrapezoidationGraph.cpp
Normal file
|
|
@ -0,0 +1,467 @@
|
|||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "SkeletalTrapezoidationGraph.hpp"
|
||||
#include <unordered_map>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include "utils/linearAlg2D.hpp"
|
||||
#include "../Line.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
STHalfEdge::STHalfEdge(SkeletalTrapezoidationEdge data) : HalfEdge(data) {}
|
||||
|
||||
bool STHalfEdge::canGoUp(bool strict) const
|
||||
{
|
||||
if (to->data.distance_to_boundary > from->data.distance_to_boundary)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (to->data.distance_to_boundary < from->data.distance_to_boundary || strict)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Edge is between equidistqant verts; recurse!
|
||||
for (edge_t* outgoing = next; outgoing != twin; outgoing = outgoing->twin->next)
|
||||
{
|
||||
if (outgoing->canGoUp())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
assert(outgoing->twin); if (!outgoing->twin) return false;
|
||||
assert(outgoing->twin->next); if (!outgoing->twin->next) return true; // This point is on the boundary?! Should never occur
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool STHalfEdge::isUpward() const
|
||||
{
|
||||
if (to->data.distance_to_boundary > from->data.distance_to_boundary)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (to->data.distance_to_boundary < from->data.distance_to_boundary)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Equidistant edge case:
|
||||
std::optional<coord_t> forward_up_dist = this->distToGoUp();
|
||||
std::optional<coord_t> backward_up_dist = twin->distToGoUp();
|
||||
if (forward_up_dist && backward_up_dist)
|
||||
{
|
||||
return forward_up_dist < backward_up_dist;
|
||||
}
|
||||
|
||||
if (forward_up_dist)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (backward_up_dist)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return to->p < from->p; // Arbitrary ordering, which returns the opposite for the twin edge
|
||||
}
|
||||
|
||||
std::optional<coord_t> STHalfEdge::distToGoUp() const
|
||||
{
|
||||
if (to->data.distance_to_boundary > from->data.distance_to_boundary)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (to->data.distance_to_boundary < from->data.distance_to_boundary)
|
||||
{
|
||||
return std::optional<coord_t>();
|
||||
}
|
||||
|
||||
// Edge is between equidistqant verts; recurse!
|
||||
std::optional<coord_t> ret;
|
||||
for (edge_t* outgoing = next; outgoing != twin; outgoing = outgoing->twin->next)
|
||||
{
|
||||
std::optional<coord_t> dist_to_up = outgoing->distToGoUp();
|
||||
if (dist_to_up)
|
||||
{
|
||||
if (ret)
|
||||
{
|
||||
ret = std::min(*ret, *dist_to_up);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = dist_to_up;
|
||||
}
|
||||
}
|
||||
assert(outgoing->twin); if (!outgoing->twin) return std::optional<coord_t>();
|
||||
assert(outgoing->twin->next); if (!outgoing->twin->next) return 0; // This point is on the boundary?! Should never occur
|
||||
}
|
||||
if (ret)
|
||||
{
|
||||
ret = *ret + (to->p - from->p).cast<int64_t>().norm();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
STHalfEdge* STHalfEdge::getNextUnconnected()
|
||||
{
|
||||
edge_t* result = static_cast<STHalfEdge*>(this);
|
||||
while (result->next)
|
||||
{
|
||||
result = result->next;
|
||||
if (result == this)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return result->twin;
|
||||
}
|
||||
|
||||
STHalfEdgeNode::STHalfEdgeNode(SkeletalTrapezoidationJoint data, Point p) : HalfEdgeNode(data, p) {}
|
||||
|
||||
bool STHalfEdgeNode::isMultiIntersection()
|
||||
{
|
||||
int odd_path_count = 0;
|
||||
edge_t* outgoing = this->incident_edge;
|
||||
do
|
||||
{
|
||||
if ( ! outgoing)
|
||||
{ // This is a node on the outside
|
||||
return false;
|
||||
}
|
||||
if (outgoing->data.isCentral())
|
||||
{
|
||||
odd_path_count++;
|
||||
}
|
||||
} while (outgoing = outgoing->twin->next, outgoing != this->incident_edge);
|
||||
return odd_path_count > 2;
|
||||
}
|
||||
|
||||
bool STHalfEdgeNode::isCentral() const
|
||||
{
|
||||
edge_t* edge = incident_edge;
|
||||
do
|
||||
{
|
||||
if (edge->data.isCentral())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
assert(edge->twin); if (!edge->twin) return false;
|
||||
} while (edge = edge->twin->next, edge != incident_edge);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool STHalfEdgeNode::isLocalMaximum(bool strict) const
|
||||
{
|
||||
if (data.distance_to_boundary == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
edge_t* edge = incident_edge;
|
||||
do
|
||||
{
|
||||
if (edge->canGoUp(strict))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
assert(edge->twin); if (!edge->twin) return false;
|
||||
|
||||
if (!edge->twin->next)
|
||||
{ // This point is on the boundary
|
||||
return false;
|
||||
}
|
||||
} while (edge = edge->twin->next, edge != incident_edge);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SkeletalTrapezoidationGraph::collapseSmallEdges(coord_t snap_dist)
|
||||
{
|
||||
std::unordered_map<edge_t*, std::list<edge_t>::iterator> edge_locator;
|
||||
std::unordered_map<node_t*, std::list<node_t>::iterator> node_locator;
|
||||
|
||||
for (auto edge_it = edges.begin(); edge_it != edges.end(); ++edge_it)
|
||||
{
|
||||
edge_locator.emplace(&*edge_it, edge_it);
|
||||
}
|
||||
|
||||
for (auto node_it = nodes.begin(); node_it != nodes.end(); ++node_it)
|
||||
{
|
||||
node_locator.emplace(&*node_it, node_it);
|
||||
}
|
||||
|
||||
auto safelyRemoveEdge = [this, &edge_locator](edge_t* to_be_removed, std::list<edge_t>::iterator& current_edge_it, bool& edge_it_is_updated)
|
||||
{
|
||||
if (current_edge_it != edges.end()
|
||||
&& to_be_removed == &*current_edge_it)
|
||||
{
|
||||
current_edge_it = edges.erase(current_edge_it);
|
||||
edge_it_is_updated = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
edges.erase(edge_locator[to_be_removed]);
|
||||
}
|
||||
};
|
||||
|
||||
auto should_collapse = [snap_dist](node_t* a, node_t* b)
|
||||
{
|
||||
return shorter_then(a->p - b->p, snap_dist);
|
||||
};
|
||||
|
||||
for (auto edge_it = edges.begin(); edge_it != edges.end();)
|
||||
{
|
||||
if (edge_it->prev)
|
||||
{
|
||||
edge_it++;
|
||||
continue;
|
||||
}
|
||||
|
||||
edge_t* quad_start = &*edge_it;
|
||||
edge_t* quad_end = quad_start; while (quad_end->next) quad_end = quad_end->next;
|
||||
edge_t* quad_mid = (quad_start->next == quad_end)? nullptr : quad_start->next;
|
||||
|
||||
bool edge_it_is_updated = false;
|
||||
if (quad_mid && should_collapse(quad_mid->from, quad_mid->to))
|
||||
{
|
||||
assert(quad_mid->twin);
|
||||
if(!quad_mid->twin)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(warning) << "Encountered quad edge without a twin.";
|
||||
continue; //Prevent accessing unallocated memory.
|
||||
}
|
||||
int count = 0;
|
||||
for (edge_t* edge_from_3 = quad_end; edge_from_3 && edge_from_3 != quad_mid->twin; edge_from_3 = edge_from_3->twin->next)
|
||||
{
|
||||
edge_from_3->from = quad_mid->from;
|
||||
edge_from_3->twin->to = quad_mid->from;
|
||||
if (count > 50)
|
||||
{
|
||||
std::cerr << edge_from_3->from->p << " - " << edge_from_3->to->p << '\n';
|
||||
}
|
||||
if (++count > 1000)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// o-o > collapse top
|
||||
// | |
|
||||
// | |
|
||||
// | |
|
||||
// o o
|
||||
if (quad_mid->from->incident_edge == quad_mid)
|
||||
{
|
||||
if (quad_mid->twin->next)
|
||||
{
|
||||
quad_mid->from->incident_edge = quad_mid->twin->next;
|
||||
}
|
||||
else
|
||||
{
|
||||
quad_mid->from->incident_edge = quad_mid->prev->twin;
|
||||
}
|
||||
}
|
||||
|
||||
nodes.erase(node_locator[quad_mid->to]);
|
||||
|
||||
quad_mid->prev->next = quad_mid->next;
|
||||
quad_mid->next->prev = quad_mid->prev;
|
||||
quad_mid->twin->next->prev = quad_mid->twin->prev;
|
||||
quad_mid->twin->prev->next = quad_mid->twin->next;
|
||||
|
||||
safelyRemoveEdge(quad_mid->twin, edge_it, edge_it_is_updated);
|
||||
safelyRemoveEdge(quad_mid, edge_it, edge_it_is_updated);
|
||||
}
|
||||
|
||||
// o-o
|
||||
// | | > collapse sides
|
||||
// o o
|
||||
if ( should_collapse(quad_start->from, quad_end->to) && should_collapse(quad_start->to, quad_end->from))
|
||||
{ // Collapse start and end edges and remove whole cell
|
||||
|
||||
quad_start->twin->to = quad_end->to;
|
||||
quad_end->to->incident_edge = quad_end->twin;
|
||||
if (quad_end->from->incident_edge == quad_end)
|
||||
{
|
||||
if (quad_end->twin->next)
|
||||
{
|
||||
quad_end->from->incident_edge = quad_end->twin->next;
|
||||
}
|
||||
else
|
||||
{
|
||||
quad_end->from->incident_edge = quad_end->prev->twin;
|
||||
}
|
||||
}
|
||||
nodes.erase(node_locator[quad_start->from]);
|
||||
|
||||
quad_start->twin->twin = quad_end->twin;
|
||||
quad_end->twin->twin = quad_start->twin;
|
||||
safelyRemoveEdge(quad_start, edge_it, edge_it_is_updated);
|
||||
safelyRemoveEdge(quad_end, edge_it, edge_it_is_updated);
|
||||
}
|
||||
// If only one side had collapsable length then the cell on the other side of that edge has to collapse
|
||||
// if we would collapse that one edge then that would change the quad_start and/or quad_end of neighboring cells
|
||||
// this is to do with the constraint that !prev == !twin.next
|
||||
|
||||
if (!edge_it_is_updated)
|
||||
{
|
||||
edge_it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletalTrapezoidationGraph::makeRib(edge_t*& prev_edge, Point start_source_point, Point end_source_point, bool is_next_to_start_or_end)
|
||||
{
|
||||
Point p;
|
||||
Line(start_source_point, end_source_point).distance_to_infinite_squared(prev_edge->to->p, &p);
|
||||
coord_t dist = (prev_edge->to->p - p).cast<int64_t>().norm();
|
||||
prev_edge->to->data.distance_to_boundary = dist;
|
||||
assert(dist >= 0);
|
||||
|
||||
nodes.emplace_front(SkeletalTrapezoidationJoint(), p);
|
||||
node_t* node = &nodes.front();
|
||||
node->data.distance_to_boundary = 0;
|
||||
|
||||
edges.emplace_front(SkeletalTrapezoidationEdge(SkeletalTrapezoidationEdge::EdgeType::EXTRA_VD));
|
||||
edge_t* forth_edge = &edges.front();
|
||||
edges.emplace_front(SkeletalTrapezoidationEdge(SkeletalTrapezoidationEdge::EdgeType::EXTRA_VD));
|
||||
edge_t* back_edge = &edges.front();
|
||||
|
||||
prev_edge->next = forth_edge;
|
||||
forth_edge->prev = prev_edge;
|
||||
forth_edge->from = prev_edge->to;
|
||||
forth_edge->to = node;
|
||||
forth_edge->twin = back_edge;
|
||||
back_edge->twin = forth_edge;
|
||||
back_edge->from = node;
|
||||
back_edge->to = prev_edge->to;
|
||||
node->incident_edge = back_edge;
|
||||
|
||||
prev_edge = back_edge;
|
||||
}
|
||||
|
||||
std::pair<SkeletalTrapezoidationGraph::edge_t*, SkeletalTrapezoidationGraph::edge_t*> SkeletalTrapezoidationGraph::insertRib(edge_t& edge, node_t* mid_node)
|
||||
{
|
||||
edge_t* edge_before = edge.prev;
|
||||
edge_t* edge_after = edge.next;
|
||||
node_t* node_before = edge.from;
|
||||
node_t* node_after = edge.to;
|
||||
|
||||
Point p = mid_node->p;
|
||||
|
||||
const Line source_segment = getSource(edge);
|
||||
Point px;
|
||||
source_segment.distance_to_squared(p, &px);
|
||||
coord_t dist = (p - px).cast<int64_t>().norm();
|
||||
assert(dist > 0);
|
||||
mid_node->data.distance_to_boundary = dist;
|
||||
mid_node->data.transition_ratio = 0; // Both transition end should have rest = 0, because at the ends a whole number of beads fits without rest
|
||||
|
||||
nodes.emplace_back(SkeletalTrapezoidationJoint(), px);
|
||||
node_t* source_node = &nodes.back();
|
||||
source_node->data.distance_to_boundary = 0;
|
||||
|
||||
edge_t* first = &edge;
|
||||
edges.emplace_back(SkeletalTrapezoidationEdge());
|
||||
edge_t* second = &edges.back();
|
||||
edges.emplace_back(SkeletalTrapezoidationEdge(SkeletalTrapezoidationEdge::EdgeType::TRANSITION_END));
|
||||
edge_t* outward_edge = &edges.back();
|
||||
edges.emplace_back(SkeletalTrapezoidationEdge(SkeletalTrapezoidationEdge::EdgeType::TRANSITION_END));
|
||||
edge_t* inward_edge = &edges.back();
|
||||
|
||||
if (edge_before)
|
||||
{
|
||||
edge_before->next = first;
|
||||
}
|
||||
first->next = outward_edge;
|
||||
outward_edge->next = nullptr;
|
||||
inward_edge->next = second;
|
||||
second->next = edge_after;
|
||||
|
||||
if (edge_after)
|
||||
{
|
||||
edge_after->prev = second;
|
||||
}
|
||||
second->prev = inward_edge;
|
||||
inward_edge->prev = nullptr;
|
||||
outward_edge->prev = first;
|
||||
first->prev = edge_before;
|
||||
|
||||
first->to = mid_node;
|
||||
outward_edge->to = source_node;
|
||||
inward_edge->to = mid_node;
|
||||
second->to = node_after;
|
||||
|
||||
first->from = node_before;
|
||||
outward_edge->from = mid_node;
|
||||
inward_edge->from = source_node;
|
||||
second->from = mid_node;
|
||||
|
||||
node_before->incident_edge = first;
|
||||
mid_node->incident_edge = outward_edge;
|
||||
source_node->incident_edge = inward_edge;
|
||||
if (edge_after)
|
||||
{
|
||||
node_after->incident_edge = edge_after;
|
||||
}
|
||||
|
||||
first->data.setIsCentral(true);
|
||||
outward_edge->data.setIsCentral(false); // TODO verify this is always the case.
|
||||
inward_edge->data.setIsCentral(false);
|
||||
second->data.setIsCentral(true);
|
||||
|
||||
outward_edge->twin = inward_edge;
|
||||
inward_edge->twin = outward_edge;
|
||||
|
||||
first->twin = nullptr; // we don't know these yet!
|
||||
second->twin = nullptr;
|
||||
|
||||
assert(second->prev->from->data.distance_to_boundary == 0);
|
||||
|
||||
return std::make_pair(first, second);
|
||||
}
|
||||
|
||||
SkeletalTrapezoidationGraph::edge_t* SkeletalTrapezoidationGraph::insertNode(edge_t* edge, Point mid, coord_t mide_node_bead_count)
|
||||
{
|
||||
edge_t* last_edge_replacing_input = edge;
|
||||
|
||||
nodes.emplace_back(SkeletalTrapezoidationJoint(), mid);
|
||||
node_t* mid_node = &nodes.back();
|
||||
|
||||
edge_t* twin = last_edge_replacing_input->twin;
|
||||
last_edge_replacing_input->twin = nullptr;
|
||||
twin->twin = nullptr;
|
||||
std::pair<edge_t*, edge_t*> left_pair = insertRib(*last_edge_replacing_input, mid_node);
|
||||
std::pair<edge_t*, edge_t*> right_pair = insertRib(*twin, mid_node);
|
||||
edge_t* first_edge_replacing_input = left_pair.first;
|
||||
last_edge_replacing_input = left_pair.second;
|
||||
edge_t* first_edge_replacing_twin = right_pair.first;
|
||||
edge_t* last_edge_replacing_twin = right_pair.second;
|
||||
|
||||
first_edge_replacing_input->twin = last_edge_replacing_twin;
|
||||
last_edge_replacing_twin->twin = first_edge_replacing_input;
|
||||
last_edge_replacing_input->twin = first_edge_replacing_twin;
|
||||
first_edge_replacing_twin->twin = last_edge_replacing_input;
|
||||
|
||||
mid_node->data.bead_count = mide_node_bead_count;
|
||||
|
||||
return last_edge_replacing_input;
|
||||
}
|
||||
|
||||
Line SkeletalTrapezoidationGraph::getSource(const edge_t &edge) const
|
||||
{
|
||||
const edge_t *from_edge = &edge;
|
||||
while (from_edge->prev)
|
||||
from_edge = from_edge->prev;
|
||||
|
||||
const edge_t *to_edge = &edge;
|
||||
while (to_edge->next)
|
||||
to_edge = to_edge->next;
|
||||
|
||||
return Line(from_edge->from->p, to_edge->to->p);
|
||||
}
|
||||
|
||||
}
|
||||
105
src/libslic3r/Arachne/SkeletalTrapezoidationGraph.hpp
Normal file
105
src/libslic3r/Arachne/SkeletalTrapezoidationGraph.hpp
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef SKELETAL_TRAPEZOIDATION_GRAPH_H
|
||||
#define SKELETAL_TRAPEZOIDATION_GRAPH_H
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "utils/HalfEdgeGraph.hpp"
|
||||
#include "SkeletalTrapezoidationEdge.hpp"
|
||||
#include "SkeletalTrapezoidationJoint.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
class STHalfEdgeNode;
|
||||
|
||||
class STHalfEdge : public HalfEdge<SkeletalTrapezoidationJoint, SkeletalTrapezoidationEdge, STHalfEdgeNode, STHalfEdge>
|
||||
{
|
||||
using edge_t = STHalfEdge;
|
||||
using node_t = STHalfEdgeNode;
|
||||
public:
|
||||
STHalfEdge(SkeletalTrapezoidationEdge data);
|
||||
|
||||
/*!
|
||||
* Check (recursively) whether there is any upward edge from the distance_to_boundary of the from of the \param edge
|
||||
*
|
||||
* \param strict Whether equidistant edges can count as a local maximum
|
||||
*/
|
||||
bool canGoUp(bool strict = false) const;
|
||||
|
||||
/*!
|
||||
* Check whether the edge goes from a lower to a higher distance_to_boundary.
|
||||
* Effectively deals with equidistant edges by looking beyond this edge.
|
||||
*/
|
||||
bool isUpward() const;
|
||||
|
||||
/*!
|
||||
* Calculate the traversed distance until we meet an upward edge.
|
||||
* Useful for calling on edges between equidistant points.
|
||||
*
|
||||
* If we can go up then the distance includes the length of the \param edge
|
||||
*/
|
||||
std::optional<coord_t> distToGoUp() const;
|
||||
|
||||
STHalfEdge* getNextUnconnected();
|
||||
};
|
||||
|
||||
class STHalfEdgeNode : public HalfEdgeNode<SkeletalTrapezoidationJoint, SkeletalTrapezoidationEdge, STHalfEdgeNode, STHalfEdge>
|
||||
{
|
||||
using edge_t = STHalfEdge;
|
||||
using node_t = STHalfEdgeNode;
|
||||
public:
|
||||
STHalfEdgeNode(SkeletalTrapezoidationJoint data, Point p);
|
||||
|
||||
bool isMultiIntersection();
|
||||
|
||||
bool isCentral() const;
|
||||
|
||||
/*!
|
||||
* Check whether this node has a locally maximal distance_to_boundary
|
||||
*
|
||||
* \param strict Whether equidistant edges can count as a local maximum
|
||||
*/
|
||||
bool isLocalMaximum(bool strict = false) const;
|
||||
};
|
||||
|
||||
class SkeletalTrapezoidationGraph: public HalfEdgeGraph<SkeletalTrapezoidationJoint, SkeletalTrapezoidationEdge, STHalfEdgeNode, STHalfEdge>
|
||||
{
|
||||
using edge_t = STHalfEdge;
|
||||
using node_t = STHalfEdgeNode;
|
||||
public:
|
||||
|
||||
/*!
|
||||
* If an edge is too small, collapse it and its twin and fix the surrounding edges to ensure a consistent graph.
|
||||
*
|
||||
* Don't collapse support edges, unless we can collapse the whole quad.
|
||||
*
|
||||
* o-,
|
||||
* | "-o
|
||||
* | | > Don't collapse this edge only.
|
||||
* o o
|
||||
*/
|
||||
void collapseSmallEdges(coord_t snap_dist = 5);
|
||||
|
||||
void makeRib(edge_t*& prev_edge, Point start_source_point, Point end_source_point, bool is_next_to_start_or_end);
|
||||
|
||||
/*!
|
||||
* Insert a node into the graph and connect it to the input polygon using ribs
|
||||
*
|
||||
* \return the last edge which replaced [edge], which points to the same [to] node
|
||||
*/
|
||||
edge_t* insertNode(edge_t* edge, Point mid, coord_t mide_node_bead_count);
|
||||
|
||||
/*!
|
||||
* Return the first and last edge of the edges replacing \p edge pointing to the same node
|
||||
*/
|
||||
std::pair<edge_t*, edge_t*> insertRib(edge_t& edge, node_t* mid_node);
|
||||
|
||||
protected:
|
||||
Line getSource(const edge_t& edge) const;
|
||||
};
|
||||
|
||||
}
|
||||
#endif
|
||||
60
src/libslic3r/Arachne/SkeletalTrapezoidationJoint.hpp
Normal file
60
src/libslic3r/Arachne/SkeletalTrapezoidationJoint.hpp
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef SKELETAL_TRAPEZOIDATION_JOINT_H
|
||||
#define SKELETAL_TRAPEZOIDATION_JOINT_H
|
||||
|
||||
#include <memory> // smart pointers
|
||||
|
||||
#include "libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
class SkeletalTrapezoidationJoint
|
||||
{
|
||||
using Beading = BeadingStrategy::Beading;
|
||||
public:
|
||||
struct BeadingPropagation
|
||||
{
|
||||
Beading beading;
|
||||
coord_t dist_to_bottom_source;
|
||||
coord_t dist_from_top_source;
|
||||
bool is_upward_propagated_only;
|
||||
BeadingPropagation(const Beading& beading)
|
||||
: beading(beading)
|
||||
, dist_to_bottom_source(0)
|
||||
, dist_from_top_source(0)
|
||||
, is_upward_propagated_only(false)
|
||||
{}
|
||||
};
|
||||
|
||||
coord_t distance_to_boundary;
|
||||
coord_t bead_count;
|
||||
float transition_ratio; //! The distance near the skeleton to leave free because this joint is in the middle of a transition, as a fraction of the inner bead width of the bead at the higher transition.
|
||||
SkeletalTrapezoidationJoint()
|
||||
: distance_to_boundary(-1)
|
||||
, bead_count(-1)
|
||||
, transition_ratio(0)
|
||||
{}
|
||||
|
||||
bool hasBeading() const
|
||||
{
|
||||
return beading.use_count() > 0;
|
||||
}
|
||||
void setBeading(std::shared_ptr<BeadingPropagation> storage)
|
||||
{
|
||||
beading = storage;
|
||||
}
|
||||
std::shared_ptr<BeadingPropagation> getBeading()
|
||||
{
|
||||
return beading.lock();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
std::weak_ptr<BeadingPropagation> beading;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // SKELETAL_TRAPEZOIDATION_JOINT_H
|
||||
843
src/libslic3r/Arachne/WallToolPaths.cpp
Normal file
843
src/libslic3r/Arachne/WallToolPaths.cpp
Normal file
|
|
@ -0,0 +1,843 @@
|
|||
// Copyright (c) 2022 Ultimaker B.V.
|
||||
// CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include <algorithm> //For std::partition_copy and std::min_element.
|
||||
#include <unordered_set>
|
||||
|
||||
#include "WallToolPaths.hpp"
|
||||
|
||||
#include "SkeletalTrapezoidation.hpp"
|
||||
#include "../ClipperUtils.hpp"
|
||||
#include "utils/linearAlg2D.hpp"
|
||||
#include "EdgeGrid.hpp"
|
||||
#include "utils/SparseLineGrid.hpp"
|
||||
#include "Geometry.hpp"
|
||||
#include "utils/PolylineStitcher.hpp"
|
||||
#include "SVG.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
//#define ARACHNE_STITCH_PATCH_DEBUG
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
WallToolPaths::WallToolPaths(const Polygons& outline, const coord_t bead_width_0, const coord_t bead_width_x,
|
||||
const size_t inset_count, const coord_t wall_0_inset, const WallToolPathsParams ¶ms)
|
||||
: outline(outline)
|
||||
, bead_width_0(bead_width_0)
|
||||
, bead_width_x(bead_width_x)
|
||||
, inset_count(inset_count)
|
||||
, wall_0_inset(wall_0_inset)
|
||||
, print_thin_walls(Slic3r::Arachne::fill_outline_gaps)
|
||||
, min_feature_size(scaled<coord_t>(params.min_feature_size))
|
||||
, min_bead_width(scaled<coord_t>(params.min_bead_width))
|
||||
, small_area_length(static_cast<double>(bead_width_0) / 2.)
|
||||
, toolpaths_generated(false)
|
||||
, wall_transition_filter_deviation(scaled<coord_t>(params.wall_transition_filter_deviation))
|
||||
, m_params(params)
|
||||
{
|
||||
}
|
||||
|
||||
void simplify(Polygon &thiss, const int64_t smallest_line_segment_squared, const int64_t allowed_error_distance_squared)
|
||||
{
|
||||
if (thiss.size() < 3) {
|
||||
thiss.points.clear();
|
||||
return;
|
||||
}
|
||||
if (thiss.size() == 3)
|
||||
return;
|
||||
|
||||
Polygon new_path;
|
||||
Point previous = thiss.points.back();
|
||||
Point previous_previous = thiss.points.at(thiss.points.size() - 2);
|
||||
Point current = thiss.points.at(0);
|
||||
|
||||
/* When removing a vertex, we check the height of the triangle of the area
|
||||
being removed from the original polygon by the simplification. However,
|
||||
when consecutively removing multiple vertices the height of the previously
|
||||
removed vertices w.r.t. the shortcut path changes.
|
||||
In order to not recompute the new height value of previously removed
|
||||
vertices we compute the height of a representative triangle, which covers
|
||||
the same amount of area as the area being cut off. We use the Shoelace
|
||||
formula to accumulate the area under the removed segments. This works by
|
||||
computing the area in a 'fan' where each of the blades of the fan go from
|
||||
the origin to one of the segments. While removing vertices the area in
|
||||
this fan accumulates. By subtracting the area of the blade connected to
|
||||
the short-cutting segment we obtain the total area of the cutoff region.
|
||||
From this area we compute the height of the representative triangle using
|
||||
the standard formula for a triangle area: A = .5*b*h
|
||||
*/
|
||||
int64_t accumulated_area_removed = int64_t(previous.x()) * int64_t(current.y()) - int64_t(previous.y()) * int64_t(current.x()); // Twice the Shoelace formula for area of polygon per line segment.
|
||||
|
||||
for (size_t point_idx = 0; point_idx < thiss.points.size(); point_idx++) {
|
||||
current = thiss.points.at(point_idx % thiss.points.size());
|
||||
|
||||
//Check if the accumulated area doesn't exceed the maximum.
|
||||
Point next;
|
||||
if (point_idx + 1 < thiss.points.size()) {
|
||||
next = thiss.points.at(point_idx + 1);
|
||||
} else if (point_idx + 1 == thiss.points.size() && new_path.size() > 1) { // don't spill over if the [next] vertex will then be equal to [previous]
|
||||
next = new_path[0]; //Spill over to new polygon for checking removed area.
|
||||
} else {
|
||||
next = thiss.points.at((point_idx + 1) % thiss.points.size());
|
||||
}
|
||||
const int64_t removed_area_next = int64_t(current.x()) * int64_t(next.y()) - int64_t(current.y()) * int64_t(next.x()); // Twice the Shoelace formula for area of polygon per line segment.
|
||||
const int64_t negative_area_closing = int64_t(next.x()) * int64_t(previous.y()) - int64_t(next.y()) * int64_t(previous.x()); // area between the origin and the short-cutting segment
|
||||
accumulated_area_removed += removed_area_next;
|
||||
|
||||
const int64_t length2 = (current - previous).cast<int64_t>().squaredNorm();
|
||||
if (length2 < scaled<int64_t>(25.)) {
|
||||
// We're allowed to always delete segments of less than 5 micron.
|
||||
continue;
|
||||
}
|
||||
|
||||
const int64_t area_removed_so_far = accumulated_area_removed + negative_area_closing; // close the shortcut area polygon
|
||||
const int64_t base_length_2 = (next - previous).cast<int64_t>().squaredNorm();
|
||||
|
||||
if (base_length_2 == 0) //Two line segments form a line back and forth with no area.
|
||||
continue; //Remove the vertex.
|
||||
//We want to check if the height of the triangle formed by previous, current and next vertices is less than allowed_error_distance_squared.
|
||||
//1/2 L = A [actual area is half of the computed shoelace value] // Shoelace formula is .5*(...) , but we simplify the computation and take out the .5
|
||||
//A = 1/2 * b * h [triangle area formula]
|
||||
//L = b * h [apply above two and take out the 1/2]
|
||||
//h = L / b [divide by b]
|
||||
//h^2 = (L / b)^2 [square it]
|
||||
//h^2 = L^2 / b^2 [factor the divisor]
|
||||
const int64_t height_2 = double(area_removed_so_far) * double(area_removed_so_far) / double(base_length_2);
|
||||
if ((height_2 <= Slic3r::sqr(scaled<coord_t>(0.005)) //Almost exactly colinear (barring rounding errors).
|
||||
&& Line::distance_to_infinite(current, previous, next) <= scaled<double>(0.005))) // make sure that height_2 is not small because of cancellation of positive and negative areas
|
||||
continue;
|
||||
|
||||
if (length2 < smallest_line_segment_squared
|
||||
&& height_2 <= allowed_error_distance_squared) // removing the vertex doesn't introduce too much error.)
|
||||
{
|
||||
const int64_t next_length2 = (current - next).cast<int64_t>().squaredNorm();
|
||||
if (next_length2 > 4 * smallest_line_segment_squared) {
|
||||
// Special case; The next line is long. If we were to remove this, it could happen that we get quite noticeable artifacts.
|
||||
// We should instead move this point to a location where both edges are kept and then remove the previous point that we wanted to keep.
|
||||
// By taking the intersection of these two lines, we get a point that preserves the direction (so it makes the corner a bit more pointy).
|
||||
// We just need to be sure that the intersection point does not introduce an artifact itself.
|
||||
Point intersection_point;
|
||||
bool has_intersection = Line(previous_previous, previous).intersection_infinite(Line(current, next), &intersection_point);
|
||||
if (!has_intersection
|
||||
|| Line::distance_to_infinite_squared(intersection_point, previous, current) > double(allowed_error_distance_squared)
|
||||
|| (intersection_point - previous).cast<int64_t>().squaredNorm() > smallest_line_segment_squared // The intersection point is way too far from the 'previous'
|
||||
|| (intersection_point - next).cast<int64_t>().squaredNorm() > smallest_line_segment_squared) // and 'next' points, so it shouldn't replace 'current'
|
||||
{
|
||||
// We can't find a better spot for it, but the size of the line is more than 5 micron.
|
||||
// So the only thing we can do here is leave it in...
|
||||
}
|
||||
else {
|
||||
// New point seems like a valid one.
|
||||
current = intersection_point;
|
||||
// If there was a previous point added, remove it.
|
||||
if(!new_path.empty()) {
|
||||
new_path.points.pop_back();
|
||||
previous = previous_previous;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
continue; //Remove the vertex.
|
||||
}
|
||||
}
|
||||
//Don't remove the vertex.
|
||||
accumulated_area_removed = removed_area_next; // so that in the next iteration it's the area between the origin, [previous] and [current]
|
||||
previous_previous = previous;
|
||||
previous = current; //Note that "previous" is only updated if we don't remove the vertex.
|
||||
new_path.points.push_back(current);
|
||||
}
|
||||
|
||||
thiss = new_path;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Removes vertices of the polygons to make sure that they are not too high
|
||||
* resolution.
|
||||
*
|
||||
* This removes points which are connected to line segments that are shorter
|
||||
* than the `smallest_line_segment`, unless that would introduce a deviation
|
||||
* in the contour of more than `allowed_error_distance`.
|
||||
*
|
||||
* Criteria:
|
||||
* 1. Never remove a vertex if either of the connceted segments is larger than \p smallest_line_segment
|
||||
* 2. Never remove a vertex if the distance between that vertex and the final resulting polygon would be higher than \p allowed_error_distance
|
||||
* 3. The direction of segments longer than \p smallest_line_segment always
|
||||
* remains unaltered (but their end points may change if it is connected to
|
||||
* a small segment)
|
||||
*
|
||||
* Simplify uses a heuristic and doesn't neccesarily remove all removable
|
||||
* vertices under the above criteria, but simplify may never violate these
|
||||
* criteria. Unless the segments or the distance is smaller than the
|
||||
* rounding error of 5 micron.
|
||||
*
|
||||
* Vertices which introduce an error of less than 5 microns are removed
|
||||
* anyway, even if the segments are longer than the smallest line segment.
|
||||
* This makes sure that (practically) colinear line segments are joined into
|
||||
* a single line segment.
|
||||
* \param smallest_line_segment Maximal length of removed line segments.
|
||||
* \param allowed_error_distance If removing a vertex introduces a deviation
|
||||
* from the original path that is more than this distance, the vertex may
|
||||
* not be removed.
|
||||
*/
|
||||
void simplify(Polygons &thiss, const int64_t smallest_line_segment = scaled<coord_t>(0.01), const int64_t allowed_error_distance = scaled<coord_t>(0.005))
|
||||
{
|
||||
const int64_t allowed_error_distance_squared = int64_t(allowed_error_distance) * int64_t(allowed_error_distance);
|
||||
const int64_t smallest_line_segment_squared = int64_t(smallest_line_segment) * int64_t(smallest_line_segment);
|
||||
for (size_t p = 0; p < thiss.size(); p++)
|
||||
{
|
||||
simplify(thiss[p], smallest_line_segment_squared, allowed_error_distance_squared);
|
||||
if (thiss[p].size() < 3)
|
||||
{
|
||||
thiss.erase(thiss.begin() + p);
|
||||
p--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef SparseLineGrid<PolygonsPointIndex, PolygonsPointIndexSegmentLocator> LocToLineGrid;
|
||||
std::unique_ptr<LocToLineGrid> createLocToLineGrid(const Polygons &polygons, int square_size)
|
||||
{
|
||||
unsigned int n_points = 0;
|
||||
for (const auto &poly : polygons)
|
||||
n_points += poly.size();
|
||||
|
||||
auto ret = std::make_unique<LocToLineGrid>(square_size, n_points);
|
||||
|
||||
for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++)
|
||||
for (unsigned int point_idx = 0; point_idx < polygons[poly_idx].size(); point_idx++)
|
||||
ret->insert(PolygonsPointIndex(&polygons, poly_idx, point_idx));
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Note: Also tries to solve for near-self intersections, when epsilon >= 1
|
||||
*/
|
||||
void fixSelfIntersections(const coord_t epsilon, Polygons &thiss)
|
||||
{
|
||||
if (epsilon < 1) {
|
||||
ClipperLib::SimplifyPolygons(ClipperUtils::PolygonsProvider(thiss));
|
||||
return;
|
||||
}
|
||||
|
||||
const int64_t half_epsilon = (epsilon + 1) / 2;
|
||||
|
||||
// Points too close to line segments should be moved a little away from those line segments, but less than epsilon,
|
||||
// so at least half-epsilon distance between points can still be guaranteed.
|
||||
constexpr coord_t grid_size = scaled<coord_t>(2.);
|
||||
auto query_grid = createLocToLineGrid(thiss, grid_size);
|
||||
|
||||
const auto move_dist = std::max<int64_t>(2L, half_epsilon - 2);
|
||||
const int64_t half_epsilon_sqrd = half_epsilon * half_epsilon;
|
||||
|
||||
const size_t n = thiss.size();
|
||||
for (size_t poly_idx = 0; poly_idx < n; poly_idx++) {
|
||||
const size_t pathlen = thiss[poly_idx].size();
|
||||
for (size_t point_idx = 0; point_idx < pathlen; ++point_idx) {
|
||||
Point &pt = thiss[poly_idx][point_idx];
|
||||
for (const auto &line : query_grid->getNearby(pt, epsilon)) {
|
||||
const size_t line_next_idx = (line.point_idx + 1) % thiss[line.poly_idx].size();
|
||||
if (poly_idx == line.poly_idx && (point_idx == line.point_idx || point_idx == line_next_idx))
|
||||
continue;
|
||||
|
||||
const Line segment(thiss[line.poly_idx][line.point_idx], thiss[line.poly_idx][line_next_idx]);
|
||||
Point segment_closest_point;
|
||||
segment.distance_to_squared(pt, &segment_closest_point);
|
||||
|
||||
if (half_epsilon_sqrd >= (pt - segment_closest_point).cast<int64_t>().squaredNorm()) {
|
||||
const Point &other = thiss[poly_idx][(point_idx + 1) % pathlen];
|
||||
const Vec2i64 vec = (LinearAlg2D::pointIsLeftOfLine(other, segment.a, segment.b) > 0 ? segment.b - segment.a : segment.a - segment.b).cast<int64_t>();
|
||||
assert(Slic3r::sqr(double(vec.x())) < double(std::numeric_limits<int64_t>::max()));
|
||||
assert(Slic3r::sqr(double(vec.y())) < double(std::numeric_limits<int64_t>::max()));
|
||||
const int64_t len = vec.norm();
|
||||
pt.x() += (-vec.y() * move_dist) / len;
|
||||
pt.y() += (vec.x() * move_dist) / len;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClipperLib::SimplifyPolygons(ClipperUtils::PolygonsProvider(thiss));
|
||||
}
|
||||
|
||||
/*!
|
||||
* Removes overlapping consecutive line segments which don't delimit a positive area.
|
||||
*/
|
||||
void removeDegenerateVerts(Polygons &thiss)
|
||||
{
|
||||
for (size_t poly_idx = 0; poly_idx < thiss.size(); poly_idx++) {
|
||||
Polygon &poly = thiss[poly_idx];
|
||||
Polygon result;
|
||||
|
||||
auto isDegenerate = [](const Point &last, const Point &now, const Point &next) {
|
||||
Vec2i64 last_line = (now - last).cast<int64_t>();
|
||||
Vec2i64 next_line = (next - now).cast<int64_t>();
|
||||
return last_line.dot(next_line) == -1 * last_line.norm() * next_line.norm();
|
||||
};
|
||||
bool isChanged = false;
|
||||
for (size_t idx = 0; idx < poly.size(); idx++) {
|
||||
const Point &last = (result.size() == 0) ? poly.back() : result.back();
|
||||
if (idx + 1 == poly.size() && result.size() == 0)
|
||||
break;
|
||||
|
||||
const Point &next = (idx + 1 == poly.size()) ? result[0] : poly[idx + 1];
|
||||
if (isDegenerate(last, poly[idx], next)) { // lines are in the opposite direction
|
||||
// don't add vert to the result
|
||||
isChanged = true;
|
||||
while (result.size() > 1 && isDegenerate(result[result.size() - 2], result.back(), next))
|
||||
result.points.pop_back();
|
||||
} else {
|
||||
result.points.emplace_back(poly[idx]);
|
||||
}
|
||||
}
|
||||
|
||||
if (isChanged) {
|
||||
if (result.size() > 2) {
|
||||
poly = result;
|
||||
} else {
|
||||
thiss.erase(thiss.begin() + poly_idx);
|
||||
poly_idx--; // effectively the next iteration has the same poly_idx (referring to a new poly which is not yet processed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void removeSmallAreas(Polygons &thiss, const double min_area_size, const bool remove_holes)
|
||||
{
|
||||
auto to_path = [](const Polygon &poly) -> ClipperLib::Path {
|
||||
ClipperLib::Path out;
|
||||
for (const Point &pt : poly.points)
|
||||
out.emplace_back(ClipperLib::cInt(pt.x()), ClipperLib::cInt(pt.y()));
|
||||
return out;
|
||||
};
|
||||
|
||||
auto new_end = thiss.end();
|
||||
if(remove_holes)
|
||||
{
|
||||
for(auto it = thiss.begin(); it < new_end; it++)
|
||||
{
|
||||
// All polygons smaller than target are removed by replacing them with a polygon from the back of the vector
|
||||
if(fabs(ClipperLib::Area(to_path(*it))) < min_area_size)
|
||||
{
|
||||
new_end--;
|
||||
*it = std::move(*new_end);
|
||||
it--; // wind back the iterator such that the polygon just swaped in is checked next
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// For each polygon, computes the signed area, move small outlines at the end of the vector and keep pointer on small holes
|
||||
std::vector<Polygon> small_holes;
|
||||
for(auto it = thiss.begin(); it < new_end; it++) {
|
||||
double area = ClipperLib::Area(to_path(*it));
|
||||
if (fabs(area) < min_area_size)
|
||||
{
|
||||
if(area >= 0)
|
||||
{
|
||||
new_end--;
|
||||
if(it < new_end) {
|
||||
std::swap(*new_end, *it);
|
||||
it--;
|
||||
}
|
||||
else
|
||||
{ // Don't self-swap the last Path
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
small_holes.push_back(*it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Removes small holes that have their first point inside one of the removed outlines
|
||||
// Iterating in reverse ensures that unprocessed small holes won't be moved
|
||||
const auto removed_outlines_start = new_end;
|
||||
for(auto hole_it = small_holes.rbegin(); hole_it < small_holes.rend(); hole_it++)
|
||||
{
|
||||
for(auto outline_it = removed_outlines_start; outline_it < thiss.end() ; outline_it++)
|
||||
{
|
||||
if(Polygon(*outline_it).contains(*hole_it->begin())) {
|
||||
new_end--;
|
||||
*hole_it = std::move(*new_end);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
thiss.resize(new_end-thiss.begin());
|
||||
}
|
||||
|
||||
void removeColinearEdges(Polygon &poly, const double max_deviation_angle)
|
||||
{
|
||||
// TODO: Can be made more efficient (for example, use pointer-types for process-/skip-indices, so we can swap them without copy).
|
||||
size_t num_removed_in_iteration = 0;
|
||||
do {
|
||||
num_removed_in_iteration = 0;
|
||||
std::vector<bool> process_indices(poly.points.size(), true);
|
||||
|
||||
bool go = true;
|
||||
while (go) {
|
||||
go = false;
|
||||
|
||||
const auto &rpath = poly;
|
||||
const size_t pathlen = rpath.size();
|
||||
if (pathlen <= 3)
|
||||
return;
|
||||
|
||||
std::vector<bool> skip_indices(poly.points.size(), false);
|
||||
|
||||
Polygon new_path;
|
||||
for (size_t point_idx = 0; point_idx < pathlen; ++point_idx) {
|
||||
// Don't iterate directly over process-indices, but do it this way, because there are points _in_ process-indices that should nonetheless
|
||||
// be skipped:
|
||||
if (!process_indices[point_idx]) {
|
||||
new_path.points.push_back(rpath[point_idx]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Should skip the last point for this iteration if the old first was removed (which can be seen from the fact that the new first was skipped):
|
||||
if (point_idx == (pathlen - 1) && skip_indices[0]) {
|
||||
skip_indices[new_path.size()] = true;
|
||||
go = true;
|
||||
new_path.points.push_back(rpath[point_idx]);
|
||||
break;
|
||||
}
|
||||
|
||||
const Point &prev = rpath[(point_idx - 1 + pathlen) % pathlen];
|
||||
const Point &pt = rpath[point_idx];
|
||||
const Point &next = rpath[(point_idx + 1) % pathlen];
|
||||
|
||||
float angle = LinearAlg2D::getAngleLeft(prev, pt, next); // [0 : 2 * pi]
|
||||
if (angle >= float(M_PI)) { angle -= float(M_PI); } // map [pi : 2 * pi] to [0 : pi]
|
||||
|
||||
// Check if the angle is within limits for the point to 'make sense', given the maximum deviation.
|
||||
// If the angle indicates near-parallel segments ignore the point 'pt'
|
||||
if (angle > max_deviation_angle && angle < M_PI - max_deviation_angle) {
|
||||
new_path.points.push_back(pt);
|
||||
} else if (point_idx != (pathlen - 1)) {
|
||||
// Skip the next point, since the current one was removed:
|
||||
skip_indices[new_path.size()] = true;
|
||||
go = true;
|
||||
new_path.points.push_back(next);
|
||||
++point_idx;
|
||||
}
|
||||
}
|
||||
poly = new_path;
|
||||
num_removed_in_iteration += pathlen - poly.points.size();
|
||||
|
||||
process_indices.clear();
|
||||
process_indices.insert(process_indices.end(), skip_indices.begin(), skip_indices.end());
|
||||
}
|
||||
} while (num_removed_in_iteration > 0);
|
||||
}
|
||||
|
||||
void removeColinearEdges(Polygons &thiss, const double max_deviation_angle = 0.0005)
|
||||
{
|
||||
for (int p = 0; p < int(thiss.size()); p++) {
|
||||
removeColinearEdges(thiss[p], max_deviation_angle);
|
||||
if (thiss[p].size() < 3) {
|
||||
thiss.erase(thiss.begin() + p);
|
||||
p--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<VariableWidthLines> &WallToolPaths::generate()
|
||||
{
|
||||
if (this->inset_count < 1)
|
||||
return toolpaths;
|
||||
|
||||
const coord_t smallest_segment = Slic3r::Arachne::meshfix_maximum_resolution;
|
||||
const coord_t allowed_distance = Slic3r::Arachne::meshfix_maximum_deviation;
|
||||
const coord_t epsilon_offset = (allowed_distance / 2) - 1;
|
||||
const double transitioning_angle = Geometry::deg2rad(m_params.wall_transition_angle);
|
||||
constexpr coord_t discretization_step_size = scaled<coord_t>(0.8);
|
||||
|
||||
// Simplify outline for boost::voronoi consumption. Absolutely no self intersections or near-self intersections allowed:
|
||||
// TODO: Open question: Does this indeed fix all (or all-but-one-in-a-million) cases for manifold but otherwise possibly complex polygons?
|
||||
Polygons prepared_outline = offset(offset(offset(outline, -epsilon_offset), epsilon_offset * 2), -epsilon_offset);
|
||||
simplify(prepared_outline, smallest_segment, allowed_distance);
|
||||
fixSelfIntersections(epsilon_offset, prepared_outline);
|
||||
removeDegenerateVerts(prepared_outline);
|
||||
removeColinearEdges(prepared_outline, 0.005);
|
||||
// Removing collinear edges may introduce self intersections, so we need to fix them again
|
||||
fixSelfIntersections(epsilon_offset, prepared_outline);
|
||||
removeDegenerateVerts(prepared_outline);
|
||||
removeSmallAreas(prepared_outline, small_area_length * small_area_length, false);
|
||||
|
||||
// The functions above could produce intersecting polygons that could cause a crash inside Arachne.
|
||||
// Applying Clipper union should be enough to get rid of this issue.
|
||||
// Clipper union also fixed an issue in Arachne that in post-processing Voronoi diagram, some edges
|
||||
// didn't have twin edges (this probably isn't an issue in Boost Voronoi generator).
|
||||
prepared_outline = union_(prepared_outline);
|
||||
|
||||
if (area(prepared_outline) <= 0) {
|
||||
assert(toolpaths.empty());
|
||||
return toolpaths;
|
||||
}
|
||||
|
||||
const coord_t wall_transition_length = scaled<coord_t>(this->m_params.wall_transition_length);
|
||||
const double wall_split_middle_threshold = this->m_params.wall_split_middle_threshold; // For an uneven nr. of lines: When to split the middle wall into two.
|
||||
const double wall_add_middle_threshold = this->m_params.wall_add_middle_threshold; // For an even nr. of lines: When to add a new middle in between the innermost two walls.
|
||||
const int wall_distribution_count = this->m_params.wall_distribution_count;
|
||||
const size_t max_bead_count = (inset_count < std::numeric_limits<coord_t>::max() / 2) ? 2 * inset_count : std::numeric_limits<coord_t>::max();
|
||||
const auto beading_strat = BeadingStrategyFactory::makeStrategy
|
||||
(
|
||||
bead_width_0,
|
||||
bead_width_x,
|
||||
wall_transition_length,
|
||||
transitioning_angle,
|
||||
print_thin_walls,
|
||||
min_bead_width,
|
||||
min_feature_size,
|
||||
wall_split_middle_threshold,
|
||||
wall_add_middle_threshold,
|
||||
max_bead_count,
|
||||
wall_0_inset,
|
||||
wall_distribution_count
|
||||
);
|
||||
const coord_t transition_filter_dist = scaled<coord_t>(100.f);
|
||||
const coord_t allowed_filter_deviation = wall_transition_filter_deviation;
|
||||
SkeletalTrapezoidation wall_maker
|
||||
(
|
||||
prepared_outline,
|
||||
*beading_strat,
|
||||
beading_strat->getTransitioningAngle(),
|
||||
discretization_step_size,
|
||||
transition_filter_dist,
|
||||
allowed_filter_deviation,
|
||||
wall_transition_length
|
||||
);
|
||||
wall_maker.generateToolpaths(toolpaths);
|
||||
|
||||
stitchToolPaths(toolpaths, this->bead_width_x);
|
||||
|
||||
removeSmallLines(toolpaths);
|
||||
|
||||
separateOutInnerContour();
|
||||
|
||||
simplifyToolPaths(toolpaths);
|
||||
|
||||
removeEmptyToolPaths(toolpaths);
|
||||
assert(std::is_sorted(toolpaths.cbegin(), toolpaths.cend(),
|
||||
[](const VariableWidthLines& l, const VariableWidthLines& r)
|
||||
{
|
||||
return l.front().inset_idx < r.front().inset_idx;
|
||||
}) && "WallToolPaths should be sorted from the outer 0th to inner_walls");
|
||||
toolpaths_generated = true;
|
||||
return toolpaths;
|
||||
}
|
||||
|
||||
void WallToolPaths::stitchToolPaths(std::vector<VariableWidthLines> &toolpaths, const coord_t bead_width_x)
|
||||
{
|
||||
const coord_t stitch_distance = bead_width_x - 1; //In 0-width contours, junctions can cause up to 1-line-width gaps. Don't stitch more than 1 line width.
|
||||
|
||||
for (unsigned int wall_idx = 0; wall_idx < toolpaths.size(); wall_idx++) {
|
||||
VariableWidthLines& wall_lines = toolpaths[wall_idx];
|
||||
|
||||
VariableWidthLines stitched_polylines;
|
||||
VariableWidthLines closed_polygons;
|
||||
PolylineStitcher<VariableWidthLines, ExtrusionLine, ExtrusionJunction>::stitch(wall_lines, stitched_polylines, closed_polygons, stitch_distance);
|
||||
#ifdef ARACHNE_STITCH_PATCH_DEBUG
|
||||
for (const ExtrusionLine& line : stitched_polylines) {
|
||||
if ( ! line.is_odd && line.polylineLength() > 3 * stitch_distance && line.size() > 3) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Some even contour lines could not be closed into polygons!";
|
||||
assert(false && "Some even contour lines could not be closed into polygons!");
|
||||
BoundingBox aabb;
|
||||
for (auto line2 : wall_lines)
|
||||
for (auto j : line2)
|
||||
aabb.merge(j.p);
|
||||
{
|
||||
static int iRun = 0;
|
||||
SVG svg(debug_out_path("contours_before.svg-%d.png", iRun), aabb);
|
||||
std::array<const char *, 8> colors = {"gray", "black", "blue", "green", "lime", "purple", "red", "yellow"};
|
||||
size_t color_idx = 0;
|
||||
for (auto& inset : toolpaths)
|
||||
for (auto& line2 : inset) {
|
||||
// svg.writePolyline(line2.toPolygon(), col);
|
||||
|
||||
Polygon poly = line2.toPolygon();
|
||||
Point last = poly.front();
|
||||
for (size_t idx = 1 ; idx < poly.size(); idx++) {
|
||||
Point here = poly[idx];
|
||||
svg.draw(Line(last, here), colors[color_idx]);
|
||||
// svg.draw_text((last + here) / 2, std::to_string(line2.junctions[idx].region_id).c_str(), "black");
|
||||
last = here;
|
||||
}
|
||||
svg.draw(poly[0], colors[color_idx]);
|
||||
// svg.nextLayer();
|
||||
// svg.writePoints(poly, true, 0.1);
|
||||
// svg.nextLayer();
|
||||
color_idx = (color_idx + 1) % colors.size();
|
||||
}
|
||||
}
|
||||
{
|
||||
static int iRun = 0;
|
||||
SVG svg(debug_out_path("contours-%d.svg", iRun), aabb);
|
||||
for (auto& inset : toolpaths)
|
||||
for (auto& line2 : inset)
|
||||
svg.draw_outline(line2.toPolygon(), "gray");
|
||||
for (auto& line2 : stitched_polylines) {
|
||||
const char *col = line2.is_odd ? "gray" : "red";
|
||||
if ( ! line2.is_odd)
|
||||
std::cerr << "Non-closed even wall of size: " << line2.size() << " at " << line2.front().p << "\n";
|
||||
if ( ! line2.is_odd)
|
||||
svg.draw(line2.front().p);
|
||||
Polygon poly = line2.toPolygon();
|
||||
Point last = poly.front();
|
||||
for (size_t idx = 1 ; idx < poly.size(); idx++)
|
||||
{
|
||||
Point here = poly[idx];
|
||||
svg.draw(Line(last, here), col);
|
||||
last = here;
|
||||
}
|
||||
}
|
||||
for (auto line2 : closed_polygons)
|
||||
svg.draw(line2.toPolygon());
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // ARACHNE_STITCH_PATCH_DEBUG
|
||||
wall_lines = stitched_polylines; // replace input toolpaths with stitched polylines
|
||||
|
||||
for (ExtrusionLine& wall_polygon : closed_polygons)
|
||||
{
|
||||
if (wall_polygon.junctions.empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
wall_polygon.is_closed = true;
|
||||
wall_lines.emplace_back(std::move(wall_polygon)); // add stitched polygons to result
|
||||
}
|
||||
#ifdef DEBUG
|
||||
for (ExtrusionLine& line : wall_lines)
|
||||
{
|
||||
assert(line.inset_idx == wall_idx);
|
||||
}
|
||||
#endif // DEBUG
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T> bool shorterThan(const T &shape, const coord_t check_length)
|
||||
{
|
||||
const auto *p0 = &shape.back();
|
||||
int64_t length = 0;
|
||||
for (const auto &p1 : shape) {
|
||||
length += (*p0 - p1).template cast<int64_t>().norm();
|
||||
if (length >= check_length)
|
||||
return false;
|
||||
p0 = &p1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void WallToolPaths::removeSmallLines(std::vector<VariableWidthLines> &toolpaths)
|
||||
{
|
||||
for (VariableWidthLines &inset : toolpaths) {
|
||||
for (size_t line_idx = 0; line_idx < inset.size(); line_idx++) {
|
||||
ExtrusionLine &line = inset[line_idx];
|
||||
coord_t min_width = std::numeric_limits<coord_t>::max();
|
||||
for (const ExtrusionJunction &j : line)
|
||||
min_width = std::min(min_width, j.w);
|
||||
if (line.is_odd && !line.is_closed && shorterThan(line, min_width / 2)) { // remove line
|
||||
line = std::move(inset.back());
|
||||
inset.erase(--inset.end());
|
||||
line_idx--; // reconsider the current position
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WallToolPaths::simplifyToolPaths(std::vector<VariableWidthLines> &toolpaths)
|
||||
{
|
||||
for (size_t toolpaths_idx = 0; toolpaths_idx < toolpaths.size(); ++toolpaths_idx)
|
||||
{
|
||||
const int64_t maximum_resolution = Slic3r::Arachne::meshfix_maximum_resolution;
|
||||
const int64_t maximum_deviation = Slic3r::Arachne::meshfix_maximum_deviation;
|
||||
const int64_t maximum_extrusion_area_deviation = Slic3r::Arachne::meshfix_maximum_extrusion_area_deviation; // unit: μm²
|
||||
for (auto& line : toolpaths[toolpaths_idx])
|
||||
{
|
||||
line.simplify(maximum_resolution * maximum_resolution, maximum_deviation * maximum_deviation, maximum_extrusion_area_deviation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<VariableWidthLines> &WallToolPaths::getToolPaths()
|
||||
{
|
||||
if (!toolpaths_generated)
|
||||
return generate();
|
||||
return toolpaths;
|
||||
}
|
||||
|
||||
void WallToolPaths::separateOutInnerContour()
|
||||
{
|
||||
//We'll remove all 0-width paths from the original toolpaths and store them separately as polygons.
|
||||
std::vector<VariableWidthLines> actual_toolpaths;
|
||||
actual_toolpaths.reserve(toolpaths.size()); //A bit too much, but the correct order of magnitude.
|
||||
std::vector<VariableWidthLines> contour_paths;
|
||||
contour_paths.reserve(toolpaths.size() / inset_count);
|
||||
inner_contour.clear();
|
||||
for (const VariableWidthLines &inset : toolpaths) {
|
||||
if (inset.empty())
|
||||
continue;
|
||||
bool is_contour = false;
|
||||
for (const ExtrusionLine &line : inset) {
|
||||
for (const ExtrusionJunction &j : line) {
|
||||
if (j.w == 0)
|
||||
is_contour = true;
|
||||
else
|
||||
is_contour = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_contour) {
|
||||
#ifdef DEBUG
|
||||
for (const ExtrusionLine &line : inset)
|
||||
for (const ExtrusionJunction &j : line)
|
||||
assert(j.w == 0);
|
||||
#endif // DEBUG
|
||||
for (const ExtrusionLine &line : inset) {
|
||||
if (line.is_odd)
|
||||
continue; // odd lines don't contribute to the contour
|
||||
else if (line.is_closed) // sometimes an very small even polygonal wall is not stitched into a polygon
|
||||
inner_contour.emplace_back(line.toPolygon());
|
||||
}
|
||||
} else {
|
||||
actual_toolpaths.emplace_back(inset);
|
||||
}
|
||||
}
|
||||
if (!actual_toolpaths.empty())
|
||||
toolpaths = std::move(actual_toolpaths); // Filtered out the 0-width paths.
|
||||
else
|
||||
toolpaths.clear();
|
||||
|
||||
//The output walls from the skeletal trapezoidation have no known winding order, especially if they are joined together from polylines.
|
||||
//They can be in any direction, clockwise or counter-clockwise, regardless of whether the shapes are positive or negative.
|
||||
//To get a correct shape, we need to make the outside contour positive and any holes inside negative.
|
||||
//This can be done by applying the even-odd rule to the shape. This rule is not sensitive to the winding order of the polygon.
|
||||
//The even-odd rule would be incorrect if the polygon self-intersects, but that should never be generated by the skeletal trapezoidation.
|
||||
inner_contour = union_(inner_contour, ClipperLib::PolyFillType::pftEvenOdd);
|
||||
}
|
||||
|
||||
const Polygons& WallToolPaths::getInnerContour()
|
||||
{
|
||||
if (!toolpaths_generated && inset_count > 0)
|
||||
{
|
||||
generate();
|
||||
}
|
||||
else if(inset_count == 0)
|
||||
{
|
||||
return outline;
|
||||
}
|
||||
return inner_contour;
|
||||
}
|
||||
|
||||
bool WallToolPaths::removeEmptyToolPaths(std::vector<VariableWidthLines> &toolpaths)
|
||||
{
|
||||
toolpaths.erase(std::remove_if(toolpaths.begin(), toolpaths.end(), [](const VariableWidthLines& lines)
|
||||
{
|
||||
return lines.empty();
|
||||
}), toolpaths.end());
|
||||
return toolpaths.empty();
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get the order constraints of the insets when printing walls per region / hole.
|
||||
* Each returned pair consists of adjacent wall lines where the left has an inset_idx one lower than the right.
|
||||
*
|
||||
* Odd walls should always go after their enclosing wall polygons.
|
||||
*
|
||||
* \param outer_to_inner Whether the wall polygons with a lower inset_idx should go before those with a higher one.
|
||||
*/
|
||||
std::unordered_set<std::pair<const ExtrusionLine *, const ExtrusionLine *>, boost::hash<std::pair<const ExtrusionLine *, const ExtrusionLine *>>> WallToolPaths::getRegionOrder(const std::vector<ExtrusionLine *> &input, const bool outer_to_inner)
|
||||
{
|
||||
std::unordered_set<std::pair<const ExtrusionLine *, const ExtrusionLine *>, boost::hash<std::pair<const ExtrusionLine *, const ExtrusionLine *>>> order_requirements;
|
||||
|
||||
// We build a grid where we map toolpath vertex locations to toolpaths,
|
||||
// so that we can easily find which two toolpaths are next to each other,
|
||||
// which is the requirement for there to be an order constraint.
|
||||
//
|
||||
// We use a PointGrid rather than a LineGrid to save on computation time.
|
||||
// In very rare cases two insets might lie next to each other without having neighboring vertices, e.g.
|
||||
// \ .
|
||||
// | / .
|
||||
// | / .
|
||||
// || .
|
||||
// | \ .
|
||||
// | \ .
|
||||
// / .
|
||||
// However, because of how Arachne works this will likely never be the case for two consecutive insets.
|
||||
// On the other hand one could imagine that two consecutive insets of a very large circle
|
||||
// could be simplify()ed such that the remaining vertices of the two insets don't align.
|
||||
// In those cases the order requirement is not captured,
|
||||
// which means that the PathOrderOptimizer *might* result in a violation of the user set path order.
|
||||
// This problem is expected to be not so severe and happen very sparsely.
|
||||
|
||||
coord_t max_line_w = 0u;
|
||||
for (const ExtrusionLine *line : input) // compute max_line_w
|
||||
for (const ExtrusionJunction &junction : *line)
|
||||
max_line_w = std::max(max_line_w, junction.w);
|
||||
if (max_line_w == 0u)
|
||||
return order_requirements;
|
||||
|
||||
struct LineLoc
|
||||
{
|
||||
ExtrusionJunction j;
|
||||
const ExtrusionLine *line;
|
||||
};
|
||||
struct Locator
|
||||
{
|
||||
Point operator()(const LineLoc &elem) { return elem.j.p; }
|
||||
};
|
||||
|
||||
// How much farther two verts may be apart due to corners.
|
||||
// This distance must be smaller than 2, because otherwise
|
||||
// we could create an order requirement between e.g.
|
||||
// wall 2 of one region and wall 3 of another region,
|
||||
// while another wall 3 of the first region would lie in between those two walls.
|
||||
// However, higher values are better against the limitations of using a PointGrid rather than a LineGrid.
|
||||
constexpr float diagonal_extension = 1.9f;
|
||||
const auto searching_radius = coord_t(max_line_w * diagonal_extension);
|
||||
using GridT = SparsePointGrid<LineLoc, Locator>;
|
||||
GridT grid(searching_radius);
|
||||
|
||||
for (const ExtrusionLine *line : input)
|
||||
for (const ExtrusionJunction &junction : *line) grid.insert(LineLoc{junction, line});
|
||||
for (const std::pair<const SquareGrid::GridPoint, LineLoc> &pair : grid) {
|
||||
const LineLoc &lineloc_here = pair.second;
|
||||
const ExtrusionLine *here = lineloc_here.line;
|
||||
Point loc_here = pair.second.j.p;
|
||||
std::vector<LineLoc> nearby_verts = grid.getNearby(loc_here, searching_radius);
|
||||
for (const LineLoc &lineloc_nearby : nearby_verts) {
|
||||
const ExtrusionLine *nearby = lineloc_nearby.line;
|
||||
if (nearby == here)
|
||||
continue;
|
||||
if (nearby->inset_idx == here->inset_idx)
|
||||
continue;
|
||||
if (nearby->inset_idx > here->inset_idx + 1)
|
||||
continue; // not directly adjacent
|
||||
if (here->inset_idx > nearby->inset_idx + 1)
|
||||
continue; // not directly adjacent
|
||||
if (!shorter_then(loc_here - lineloc_nearby.j.p, (lineloc_here.j.w + lineloc_nearby.j.w) / 2 * diagonal_extension))
|
||||
continue; // points are too far away from each other
|
||||
if (here->is_odd || nearby->is_odd) {
|
||||
if (here->is_odd && !nearby->is_odd && nearby->inset_idx < here->inset_idx)
|
||||
order_requirements.emplace(std::make_pair(nearby, here));
|
||||
if (nearby->is_odd && !here->is_odd && here->inset_idx < nearby->inset_idx)
|
||||
order_requirements.emplace(std::make_pair(here, nearby));
|
||||
} else if ((nearby->inset_idx < here->inset_idx) == outer_to_inner) {
|
||||
order_requirements.emplace(std::make_pair(nearby, here));
|
||||
} else {
|
||||
assert((nearby->inset_idx > here->inset_idx) == outer_to_inner);
|
||||
order_requirements.emplace(std::make_pair(here, nearby));
|
||||
}
|
||||
}
|
||||
}
|
||||
return order_requirements;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
139
src/libslic3r/Arachne/WallToolPaths.hpp
Normal file
139
src/libslic3r/Arachne/WallToolPaths.hpp
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
// Copyright (c) 2020 Ultimaker B.V.
|
||||
// CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef CURAENGINE_WALLTOOLPATHS_H
|
||||
#define CURAENGINE_WALLTOOLPATHS_H
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "BeadingStrategy/BeadingStrategyFactory.hpp"
|
||||
#include "utils/ExtrusionLine.hpp"
|
||||
#include "../Polygon.hpp"
|
||||
#include "../PrintConfig.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
constexpr bool fill_outline_gaps = true;
|
||||
constexpr coord_t meshfix_maximum_resolution = scaled<coord_t>(0.5);
|
||||
constexpr coord_t meshfix_maximum_deviation = scaled<coord_t>(0.025);
|
||||
constexpr coord_t meshfix_maximum_extrusion_area_deviation = scaled<coord_t>(2.);
|
||||
|
||||
class WallToolPathsParams
|
||||
{
|
||||
public:
|
||||
float min_bead_width;
|
||||
float min_feature_size;
|
||||
float wall_transition_length;
|
||||
float wall_transition_angle;
|
||||
float wall_transition_filter_deviation;
|
||||
int wall_distribution_count;
|
||||
float wall_add_middle_threshold;
|
||||
float wall_split_middle_threshold;
|
||||
};
|
||||
|
||||
class WallToolPaths
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* A class that creates the toolpaths given an outline, nominal bead width and maximum amount of walls
|
||||
* \param outline An outline of the area in which the ToolPaths are to be generated
|
||||
* \param bead_width_0 The bead width of the first wall used in the generation of the toolpaths
|
||||
* \param bead_width_x The bead width of the inner walls used in the generation of the toolpaths
|
||||
* \param inset_count The maximum number of parallel extrusion lines that make up the wall
|
||||
* \param wall_0_inset How far to inset the outer wall, to make it adhere better to other walls.
|
||||
*/
|
||||
WallToolPaths(const Polygons& outline, coord_t bead_width_0, coord_t bead_width_x, size_t inset_count, coord_t wall_0_inset, const WallToolPathsParams ¶ms);
|
||||
|
||||
/*!
|
||||
* Generates the Toolpaths
|
||||
* \return A reference to the newly create ToolPaths
|
||||
*/
|
||||
const std::vector<VariableWidthLines> &generate();
|
||||
|
||||
/*!
|
||||
* Gets the toolpaths, if this called before \p generate() it will first generate the Toolpaths
|
||||
* \return a reference to the toolpaths
|
||||
*/
|
||||
const std::vector<VariableWidthLines> &getToolPaths();
|
||||
|
||||
/*!
|
||||
* Compute the inner contour of the walls. This contour indicates where the walled area ends and its infill begins.
|
||||
* The inside can then be filled, e.g. with skin/infill for the walls of a part, or with a pattern in the case of
|
||||
* infill with extra infill walls.
|
||||
*/
|
||||
void separateOutInnerContour();
|
||||
|
||||
/*!
|
||||
* Gets the inner contour of the area which is inside of the generated tool
|
||||
* paths.
|
||||
*
|
||||
* If the walls haven't been generated yet, this will lazily call the
|
||||
* \p generate() function to generate the walls with variable width.
|
||||
* The resulting polygon will snugly match the inside of the variable-width
|
||||
* walls where the walls get limited by the LimitedBeadingStrategy to a
|
||||
* maximum wall count.
|
||||
* If there are no walls, the outline will be returned.
|
||||
* \return The inner contour of the generated walls.
|
||||
*/
|
||||
const Polygons& getInnerContour();
|
||||
|
||||
/*!
|
||||
* Removes empty paths from the toolpaths
|
||||
* \param toolpaths the VariableWidthPaths generated with \p generate()
|
||||
* \return true if there are still paths left. If all toolpaths were removed it returns false
|
||||
*/
|
||||
static bool removeEmptyToolPaths(std::vector<VariableWidthLines> &toolpaths);
|
||||
|
||||
/*!
|
||||
* Get the order constraints of the insets when printing walls per region / hole.
|
||||
* Each returned pair consists of adjacent wall lines where the left has an inset_idx one lower than the right.
|
||||
*
|
||||
* Odd walls should always go after their enclosing wall polygons.
|
||||
*
|
||||
* \param outer_to_inner Whether the wall polygons with a lower inset_idx should go before those with a higher one.
|
||||
*/
|
||||
static std::unordered_set<std::pair<const ExtrusionLine *, const ExtrusionLine *>, boost::hash<std::pair<const ExtrusionLine *, const ExtrusionLine *>>> getRegionOrder(const std::vector<ExtrusionLine *> &input, bool outer_to_inner);
|
||||
|
||||
protected:
|
||||
/*!
|
||||
* Stitch the polylines together and form closed polygons.
|
||||
*
|
||||
* Works on both toolpaths and inner contours simultaneously.
|
||||
*/
|
||||
static void stitchToolPaths(std::vector<VariableWidthLines> &toolpaths, coord_t bead_width_x);
|
||||
|
||||
/*!
|
||||
* Remove polylines shorter than half the smallest line width along that polyline.
|
||||
*/
|
||||
static void removeSmallLines(std::vector<VariableWidthLines> &toolpaths);
|
||||
|
||||
/*!
|
||||
* Simplifies the variable-width toolpaths by calling the simplify on every line in the toolpath using the provided
|
||||
* settings.
|
||||
* \param settings The settings as provided by the user
|
||||
* \return
|
||||
*/
|
||||
static void simplifyToolPaths(std::vector<VariableWidthLines> &toolpaths);
|
||||
|
||||
private:
|
||||
const Polygons& outline; //<! A reference to the outline polygon that is the designated area
|
||||
coord_t bead_width_0; //<! The nominal or first extrusion line width with which libArachne generates its walls
|
||||
coord_t bead_width_x; //<! The subsequently extrusion line width with which libArachne generates its walls if WallToolPaths was called with the nominal_bead_width Constructor this is the same as bead_width_0
|
||||
size_t inset_count; //<! The maximum number of walls to generate
|
||||
coord_t wall_0_inset; //<! How far to inset the outer wall. Should only be applied when printing the actual walls, not extra infill/skin/support walls.
|
||||
bool print_thin_walls; //<! Whether to enable the widening beading meta-strategy for thin features
|
||||
coord_t min_feature_size; //<! The minimum size of the features that can be widened by the widening beading meta-strategy. Features thinner than that will not be printed
|
||||
coord_t min_bead_width; //<! The minimum bead size to use when widening thin model features with the widening beading meta-strategy
|
||||
double small_area_length; //<! The length of the small features which are to be filtered out, this is squared into a surface
|
||||
bool toolpaths_generated; //<! Are the toolpaths generated
|
||||
std::vector<VariableWidthLines> toolpaths; //<! The generated toolpaths
|
||||
Polygons inner_contour; //<! The inner contour of the generated toolpaths
|
||||
coord_t wall_transition_filter_deviation; //!< The allowed line width deviation induced by filtering
|
||||
const WallToolPathsParams m_params;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
||||
#endif // CURAENGINE_WALLTOOLPATHS_H
|
||||
18
src/libslic3r/Arachne/utils/ExtrusionJunction.cpp
Normal file
18
src/libslic3r/Arachne/utils/ExtrusionJunction.cpp
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "ExtrusionJunction.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
bool ExtrusionJunction::operator ==(const ExtrusionJunction& other) const
|
||||
{
|
||||
return p == other.p
|
||||
&& w == other.w
|
||||
&& perimeter_index == other.perimeter_index;
|
||||
}
|
||||
|
||||
ExtrusionJunction::ExtrusionJunction(const Point p, const coord_t w, const coord_t perimeter_index) : p(p), w(w), perimeter_index(perimeter_index) {}
|
||||
|
||||
}
|
||||
59
src/libslic3r/Arachne/utils/ExtrusionJunction.hpp
Normal file
59
src/libslic3r/Arachne/utils/ExtrusionJunction.hpp
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
|
||||
#ifndef UTILS_EXTRUSION_JUNCTION_H
|
||||
#define UTILS_EXTRUSION_JUNCTION_H
|
||||
|
||||
#include "../../Point.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
/*!
|
||||
* This struct represents one vertex in an extruded path.
|
||||
*
|
||||
* It contains information on how wide the extruded path must be at this point,
|
||||
* and which perimeter it represents.
|
||||
*/
|
||||
struct ExtrusionJunction
|
||||
{
|
||||
/*!
|
||||
* The position of the centreline of the path when it reaches this junction.
|
||||
* This is the position that should end up in the g-code eventually.
|
||||
*/
|
||||
Point p;
|
||||
|
||||
/*!
|
||||
* The width of the extruded path at this junction.
|
||||
*/
|
||||
coord_t w;
|
||||
|
||||
/*!
|
||||
* Which perimeter this junction is part of.
|
||||
*
|
||||
* Perimeters are counted from the outside inwards. The outer wall has index
|
||||
* 0.
|
||||
*/
|
||||
size_t perimeter_index;
|
||||
|
||||
ExtrusionJunction(const Point p, const coord_t w, const coord_t perimeter_index);
|
||||
|
||||
bool operator==(const ExtrusionJunction& other) const;
|
||||
};
|
||||
|
||||
inline Point operator-(const ExtrusionJunction& a, const ExtrusionJunction& b)
|
||||
{
|
||||
return a.p - b.p;
|
||||
}
|
||||
|
||||
// Identity function, used to be able to make templated algorithms that do their operations on 'point-like' input.
|
||||
inline const Point& make_point(const ExtrusionJunction& ej)
|
||||
{
|
||||
return ej.p;
|
||||
}
|
||||
|
||||
using LineJunctions = std::vector<ExtrusionJunction>; //<! The junctions along a line without further information. See \ref ExtrusionLine for a more extensive class.
|
||||
|
||||
}
|
||||
#endif // UTILS_EXTRUSION_JUNCTION_H
|
||||
280
src/libslic3r/Arachne/utils/ExtrusionLine.cpp
Normal file
280
src/libslic3r/Arachne/utils/ExtrusionLine.cpp
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "ExtrusionLine.hpp"
|
||||
#include "linearAlg2D.hpp"
|
||||
#include "../../VariableWidth.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
ExtrusionLine::ExtrusionLine(const size_t inset_idx, const bool is_odd) : inset_idx(inset_idx), is_odd(is_odd), is_closed(false) {}
|
||||
|
||||
int64_t ExtrusionLine::getLength() const
|
||||
{
|
||||
if (junctions.empty())
|
||||
return 0;
|
||||
|
||||
int64_t len = 0;
|
||||
ExtrusionJunction prev = junctions.front();
|
||||
for (const ExtrusionJunction &next : junctions) {
|
||||
len += (next.p - prev.p).cast<int64_t>().norm();
|
||||
prev = next;
|
||||
}
|
||||
if (is_closed)
|
||||
len += (front().p - back().p).cast<int64_t>().norm();
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
coord_t ExtrusionLine::getMinimalWidth() const
|
||||
{
|
||||
return std::min_element(junctions.cbegin(), junctions.cend(),
|
||||
[](const ExtrusionJunction& l, const ExtrusionJunction& r)
|
||||
{
|
||||
return l.w < r.w;
|
||||
})->w;
|
||||
}
|
||||
|
||||
void ExtrusionLine::simplify(const int64_t smallest_line_segment_squared, const int64_t allowed_error_distance_squared, const int64_t maximum_extrusion_area_deviation)
|
||||
{
|
||||
const size_t min_path_size = is_closed ? 3 : 2;
|
||||
if (junctions.size() <= min_path_size)
|
||||
return;
|
||||
|
||||
// TODO: allow for the first point to be removed in case of simplifying closed Extrusionlines.
|
||||
|
||||
/* ExtrusionLines are treated as (open) polylines, so in case an ExtrusionLine is actually a closed polygon, its
|
||||
* starting and ending points will be equal (or almost equal). Therefore, the simplification of the ExtrusionLine
|
||||
* should not touch the first and last points. As a result, start simplifying from point at index 1.
|
||||
* */
|
||||
std::vector<ExtrusionJunction> new_junctions;
|
||||
// Starting junction should always exist in the simplified path
|
||||
new_junctions.emplace_back(junctions.front());
|
||||
|
||||
/* Initially, previous_previous is always the same as previous because, for open ExtrusionLines the last junction
|
||||
* cannot be taken into consideration when checking the points at index 1. For closed ExtrusionLines, the first and
|
||||
* last junctions are anyway the same.
|
||||
* */
|
||||
ExtrusionJunction previous_previous = junctions.front();
|
||||
ExtrusionJunction previous = junctions.front();
|
||||
|
||||
/* When removing a vertex, we check the height of the triangle of the area
|
||||
being removed from the original polygon by the simplification. However,
|
||||
when consecutively removing multiple vertices the height of the previously
|
||||
removed vertices w.r.t. the shortcut path changes.
|
||||
In order to not recompute the new height value of previously removed
|
||||
vertices we compute the height of a representative triangle, which covers
|
||||
the same amount of area as the area being cut off. We use the Shoelace
|
||||
formula to accumulate the area under the removed segments. This works by
|
||||
computing the area in a 'fan' where each of the blades of the fan go from
|
||||
the origin to one of the segments. While removing vertices the area in
|
||||
this fan accumulates. By subtracting the area of the blade connected to
|
||||
the short-cutting segment we obtain the total area of the cutoff region.
|
||||
From this area we compute the height of the representative triangle using
|
||||
the standard formula for a triangle area: A = .5*b*h
|
||||
*/
|
||||
const ExtrusionJunction& initial = junctions.at(1);
|
||||
int64_t accumulated_area_removed = int64_t(previous.p.x()) * int64_t(initial.p.y()) - int64_t(previous.p.y()) * int64_t(initial.p.x()); // Twice the Shoelace formula for area of polygon per line segment.
|
||||
|
||||
for (size_t point_idx = 1; point_idx < junctions.size() - 1; point_idx++)
|
||||
{
|
||||
const ExtrusionJunction& current = junctions[point_idx];
|
||||
|
||||
// Spill over in case of overflow, unless the [next] vertex will then be equal to [previous].
|
||||
const bool spill_over = point_idx + 1 == junctions.size() && new_junctions.size() > 1;
|
||||
ExtrusionJunction& next = spill_over ? new_junctions[0] : junctions[point_idx + 1];
|
||||
|
||||
const int64_t removed_area_next = int64_t(current.p.x()) * int64_t(next.p.y()) - int64_t(current.p.y()) * int64_t(next.p.x()); // Twice the Shoelace formula for area of polygon per line segment.
|
||||
const int64_t negative_area_closing = int64_t(next.p.x()) * int64_t(previous.p.y()) - int64_t(next.p.y()) * int64_t(previous.p.x()); // Area between the origin and the short-cutting segment
|
||||
accumulated_area_removed += removed_area_next;
|
||||
|
||||
const int64_t length2 = (current - previous).cast<int64_t>().squaredNorm();
|
||||
if (length2 < scaled<coord_t>(0.025))
|
||||
{
|
||||
// We're allowed to always delete segments of less than 5 micron. The width in this case doesn't matter that much.
|
||||
continue;
|
||||
}
|
||||
|
||||
const int64_t area_removed_so_far = accumulated_area_removed + negative_area_closing; // Close the shortcut area polygon
|
||||
const int64_t base_length_2 = (next - previous).cast<int64_t>().squaredNorm();
|
||||
|
||||
if (base_length_2 == 0) // Two line segments form a line back and forth with no area.
|
||||
{
|
||||
continue; // Remove the junction (vertex).
|
||||
}
|
||||
//We want to check if the height of the triangle formed by previous, current and next vertices is less than allowed_error_distance_squared.
|
||||
//1/2 L = A [actual area is half of the computed shoelace value] // Shoelace formula is .5*(...) , but we simplify the computation and take out the .5
|
||||
//A = 1/2 * b * h [triangle area formula]
|
||||
//L = b * h [apply above two and take out the 1/2]
|
||||
//h = L / b [divide by b]
|
||||
//h^2 = (L / b)^2 [square it]
|
||||
//h^2 = L^2 / b^2 [factor the divisor]
|
||||
const auto height_2 = int64_t(double(area_removed_so_far) * double(area_removed_so_far) / double(base_length_2));
|
||||
coord_t weighted_average_width;
|
||||
const int64_t extrusion_area_error = calculateExtrusionAreaDeviationError(previous, current, next, weighted_average_width);
|
||||
if ((height_2 <= scaled<coord_t>(0.001) //Almost exactly colinear (barring rounding errors).
|
||||
&& Line::distance_to_infinite(current.p, previous.p, next.p) <= scaled<double>(0.001)) // Make sure that height_2 is not small because of cancellation of positive and negative areas
|
||||
// We shouldn't remove middle junctions of colinear segments if the area changed for the C-P segment is exceeding the maximum allowed
|
||||
&& extrusion_area_error <= maximum_extrusion_area_deviation)
|
||||
{
|
||||
// Remove the current junction (vertex).
|
||||
continue;
|
||||
}
|
||||
|
||||
if (length2 < smallest_line_segment_squared
|
||||
&& height_2 <= allowed_error_distance_squared) // Removing the junction (vertex) doesn't introduce too much error.
|
||||
{
|
||||
const int64_t next_length2 = (current - next).cast<int64_t>().squaredNorm();
|
||||
if (next_length2 > 4 * smallest_line_segment_squared)
|
||||
{
|
||||
// Special case; The next line is long. If we were to remove this, it could happen that we get quite noticeable artifacts.
|
||||
// We should instead move this point to a location where both edges are kept and then remove the previous point that we wanted to keep.
|
||||
// By taking the intersection of these two lines, we get a point that preserves the direction (so it makes the corner a bit more pointy).
|
||||
// We just need to be sure that the intersection point does not introduce an artifact itself.
|
||||
Point intersection_point;
|
||||
bool has_intersection = Line(previous_previous.p, previous.p).intersection_infinite(Line(current.p, next.p), &intersection_point);
|
||||
if (!has_intersection
|
||||
|| Line::distance_to_infinite_squared(intersection_point, previous.p, current.p) > double(allowed_error_distance_squared)
|
||||
|| (intersection_point - previous.p).cast<int64_t>().squaredNorm() > smallest_line_segment_squared // The intersection point is way too far from the 'previous'
|
||||
|| (intersection_point - next.p).cast<int64_t>().squaredNorm() > smallest_line_segment_squared) // and 'next' points, so it shouldn't replace 'current'
|
||||
{
|
||||
// We can't find a better spot for it, but the size of the line is more than 5 micron.
|
||||
// So the only thing we can do here is leave it in...
|
||||
}
|
||||
else
|
||||
{
|
||||
// New point seems like a valid one.
|
||||
const ExtrusionJunction new_to_add = ExtrusionJunction(intersection_point, current.w, current.perimeter_index);
|
||||
// If there was a previous point added, remove it.
|
||||
if(!new_junctions.empty())
|
||||
{
|
||||
new_junctions.pop_back();
|
||||
previous = previous_previous;
|
||||
}
|
||||
|
||||
// The junction (vertex) is replaced by the new one.
|
||||
accumulated_area_removed = removed_area_next; // So that in the next iteration it's the area between the origin, [previous] and [current]
|
||||
previous_previous = previous;
|
||||
previous = new_to_add; // Note that "previous" is only updated if we don't remove the junction (vertex).
|
||||
new_junctions.push_back(new_to_add);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
continue; // Remove the junction (vertex).
|
||||
}
|
||||
}
|
||||
// The junction (vertex) isn't removed.
|
||||
accumulated_area_removed = removed_area_next; // So that in the next iteration it's the area between the origin, [previous] and [current]
|
||||
previous_previous = previous;
|
||||
previous = current; // Note that "previous" is only updated if we don't remove the junction (vertex).
|
||||
new_junctions.push_back(current);
|
||||
}
|
||||
|
||||
// Ending junction (vertex) should always exist in the simplified path
|
||||
new_junctions.emplace_back(junctions.back());
|
||||
|
||||
/* In case this is a closed polygon (instead of a poly-line-segments), the invariant that the first and last points are the same should be enforced.
|
||||
* Since one of them didn't move, and the other can't have been moved further than the constraints, if originally equal, they can simply be equated.
|
||||
*/
|
||||
if ((junctions.front().p - junctions.back().p).cast<int64_t>().squaredNorm() == 0)
|
||||
{
|
||||
new_junctions.back().p = junctions.front().p;
|
||||
}
|
||||
|
||||
junctions = new_junctions;
|
||||
}
|
||||
|
||||
int64_t ExtrusionLine::calculateExtrusionAreaDeviationError(ExtrusionJunction A, ExtrusionJunction B, ExtrusionJunction C, coord_t& weighted_average_width)
|
||||
{
|
||||
/*
|
||||
* A B C A C
|
||||
* --------------- **************
|
||||
* | | ------------------------------------------
|
||||
* | |--------------------------| B removed | |***************************|
|
||||
* | | | ---------> | | |
|
||||
* | |--------------------------| | |***************************|
|
||||
* | | ------------------------------------------
|
||||
* --------------- ^ **************
|
||||
* ^ B.w + C.w / 2 ^
|
||||
* A.w + B.w / 2 new_width = weighted_average_width
|
||||
*
|
||||
*
|
||||
* ******** denote the total extrusion area deviation error in the consecutive segments as a result of using the
|
||||
* weighted-average width for the entire extrusion line.
|
||||
*
|
||||
* */
|
||||
const int64_t ab_length = (B - A).cast<int64_t>().norm();
|
||||
const int64_t bc_length = (C - B).cast<int64_t>().norm();
|
||||
const coord_t width_diff = std::max(std::abs(B.w - A.w), std::abs(C.w - B.w));
|
||||
if (width_diff > 1)
|
||||
{
|
||||
// Adjust the width only if there is a difference, or else the rounding errors may produce the wrong
|
||||
// weighted average value.
|
||||
const int64_t ab_weight = (A.w + B.w) / 2;
|
||||
const int64_t bc_weight = (B.w + C.w) / 2;
|
||||
assert(((ab_length * ab_weight + bc_length * bc_weight) / (C - A).cast<int64_t>().norm()) <= std::numeric_limits<coord_t>::max());
|
||||
weighted_average_width = (ab_length * ab_weight + bc_length * bc_weight) / (C - A).cast<int64_t>().norm();
|
||||
assert((int64_t(std::abs(ab_weight - weighted_average_width)) * ab_length + int64_t(std::abs(bc_weight - weighted_average_width)) * bc_length) <= double(std::numeric_limits<int64_t>::max()));
|
||||
return std::abs(ab_weight - weighted_average_width) * ab_length + std::abs(bc_weight - weighted_average_width) * bc_length;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the width difference is very small, then select the width of the segment that is longer
|
||||
weighted_average_width = ab_length > bc_length ? A.w : B.w;
|
||||
assert((int64_t(width_diff) * int64_t(bc_length)) <= std::numeric_limits<coord_t>::max());
|
||||
assert((int64_t(width_diff) * int64_t(ab_length)) <= std::numeric_limits<coord_t>::max());
|
||||
return ab_length > bc_length ? width_diff * bc_length : width_diff * ab_length;
|
||||
}
|
||||
}
|
||||
|
||||
bool ExtrusionLine::is_contour() const
|
||||
{
|
||||
if (!this->is_closed)
|
||||
return false;
|
||||
|
||||
Polygon poly;
|
||||
poly.points.reserve(this->junctions.size());
|
||||
for (const ExtrusionJunction &junction : this->junctions)
|
||||
poly.points.emplace_back(junction.p);
|
||||
|
||||
// Arachne produces contour with clockwise orientation and holes with counterclockwise orientation.
|
||||
return poly.is_clockwise();
|
||||
}
|
||||
|
||||
double ExtrusionLine::area() const
|
||||
{
|
||||
assert(this->is_closed);
|
||||
double a = 0.;
|
||||
if (this->junctions.size() >= 3) {
|
||||
Vec2d p1 = this->junctions.back().p.cast<double>();
|
||||
for (const ExtrusionJunction &junction : this->junctions) {
|
||||
Vec2d p2 = junction.p.cast<double>();
|
||||
a += cross2(p1, p2);
|
||||
p1 = p2;
|
||||
}
|
||||
}
|
||||
return 0.5 * a;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
||||
namespace Slic3r {
|
||||
void extrusion_paths_append(ExtrusionPaths &dst, const ClipperLib_Z::Paths &extrusion_paths, const ExtrusionRole role, const Flow &flow)
|
||||
{
|
||||
for (const ClipperLib_Z::Path &extrusion_path : extrusion_paths) {
|
||||
ThickPolyline thick_polyline = Arachne::to_thick_polyline(extrusion_path);
|
||||
Slic3r::append(dst, thick_polyline_to_extrusion_paths(thick_polyline, role, flow, scaled<float>(0.05), SCALED_EPSILON));
|
||||
}
|
||||
}
|
||||
|
||||
void extrusion_paths_append(ExtrusionPaths &dst, const Arachne::ExtrusionLine &extrusion, const ExtrusionRole role, const Flow &flow)
|
||||
{
|
||||
ThickPolyline thick_polyline = Arachne::to_thick_polyline(extrusion);
|
||||
Slic3r::append(dst, thick_polyline_to_extrusion_paths(thick_polyline, role, flow, scaled<float>(0.05), SCALED_EPSILON));
|
||||
}
|
||||
} // namespace Slic3r
|
||||
307
src/libslic3r/Arachne/utils/ExtrusionLine.hpp
Normal file
307
src/libslic3r/Arachne/utils/ExtrusionLine.hpp
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
|
||||
#ifndef UTILS_EXTRUSION_LINE_H
|
||||
#define UTILS_EXTRUSION_LINE_H
|
||||
|
||||
#include "ExtrusionJunction.hpp"
|
||||
#include "../../Polyline.hpp"
|
||||
#include "../../Polygon.hpp"
|
||||
#include "../../BoundingBox.hpp"
|
||||
#include "../../ExtrusionEntity.hpp"
|
||||
#include "../../Flow.hpp"
|
||||
#include "../../../clipper/clipper_z.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
class ThickPolyline;
|
||||
}
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
/*!
|
||||
* Represents a polyline (not just a line) that is to be extruded with variable
|
||||
* line width.
|
||||
*
|
||||
* This polyline is a sequence of \ref ExtrusionJunction, with a bit of metadata
|
||||
* about which inset it represents.
|
||||
*/
|
||||
struct ExtrusionLine
|
||||
{
|
||||
/*!
|
||||
* Which inset this path represents, counted from the outside inwards.
|
||||
*
|
||||
* The outer wall has index 0.
|
||||
*/
|
||||
size_t inset_idx;
|
||||
|
||||
/*!
|
||||
* If a thin piece needs to be printed with an odd number of walls (e.g. 5
|
||||
* walls) then there will be one wall in the middle that is not a loop. This
|
||||
* field indicates whether this path is such a line through the middle, that
|
||||
* has no companion line going back on the other side and is not a closed
|
||||
* loop.
|
||||
*/
|
||||
bool is_odd;
|
||||
|
||||
/*!
|
||||
* Whether this is a closed polygonal path
|
||||
*/
|
||||
bool is_closed;
|
||||
|
||||
/*!
|
||||
* Gets the number of vertices in this polygon.
|
||||
* \return The number of vertices in this polygon.
|
||||
*/
|
||||
size_t size() const { return junctions.size(); }
|
||||
|
||||
/*!
|
||||
* Whether there are no junctions.
|
||||
*/
|
||||
bool empty() const { return junctions.empty(); }
|
||||
|
||||
/*!
|
||||
* The list of vertices along which this path runs.
|
||||
*
|
||||
* Each junction has a width, making this path a variable-width path.
|
||||
*/
|
||||
std::vector<ExtrusionJunction> junctions;
|
||||
|
||||
ExtrusionLine(const size_t inset_idx, const bool is_odd);
|
||||
ExtrusionLine() : inset_idx(-1), is_odd(true), is_closed(false) {}
|
||||
ExtrusionLine(const ExtrusionLine &other) : inset_idx(other.inset_idx), is_odd(other.is_odd), is_closed(other.is_closed), junctions(other.junctions) {}
|
||||
|
||||
ExtrusionLine &operator=(ExtrusionLine &&other)
|
||||
{
|
||||
junctions = std::move(other.junctions);
|
||||
inset_idx = other.inset_idx;
|
||||
is_odd = other.is_odd;
|
||||
is_closed = other.is_closed;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ExtrusionLine &operator=(const ExtrusionLine &other)
|
||||
{
|
||||
junctions = other.junctions;
|
||||
inset_idx = other.inset_idx;
|
||||
is_odd = other.is_odd;
|
||||
is_closed = other.is_closed;
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::vector<ExtrusionJunction>::const_iterator begin() const { return junctions.begin(); }
|
||||
std::vector<ExtrusionJunction>::const_iterator end() const { return junctions.end(); }
|
||||
std::vector<ExtrusionJunction>::const_reverse_iterator rbegin() const { return junctions.rbegin(); }
|
||||
std::vector<ExtrusionJunction>::const_reverse_iterator rend() const { return junctions.rend(); }
|
||||
std::vector<ExtrusionJunction>::const_reference front() const { return junctions.front(); }
|
||||
std::vector<ExtrusionJunction>::const_reference back() const { return junctions.back(); }
|
||||
const ExtrusionJunction &operator[](unsigned int index) const { return junctions[index]; }
|
||||
ExtrusionJunction &operator[](unsigned int index) { return junctions[index]; }
|
||||
std::vector<ExtrusionJunction>::iterator begin() { return junctions.begin(); }
|
||||
std::vector<ExtrusionJunction>::iterator end() { return junctions.end(); }
|
||||
std::vector<ExtrusionJunction>::reference front() { return junctions.front(); }
|
||||
std::vector<ExtrusionJunction>::reference back() { return junctions.back(); }
|
||||
|
||||
template<typename... Args> void emplace_back(Args &&...args) { junctions.emplace_back(args...); }
|
||||
void remove(unsigned int index) { junctions.erase(junctions.begin() + index); }
|
||||
void insert(size_t index, const ExtrusionJunction &p) { junctions.insert(junctions.begin() + index, p); }
|
||||
|
||||
template<class iterator>
|
||||
std::vector<ExtrusionJunction>::iterator insert(std::vector<ExtrusionJunction>::const_iterator pos, iterator first, iterator last)
|
||||
{
|
||||
return junctions.insert(pos, first, last);
|
||||
}
|
||||
|
||||
void clear() { junctions.clear(); }
|
||||
void reverse() { std::reverse(junctions.begin(), junctions.end()); }
|
||||
|
||||
/*!
|
||||
* Sum the total length of this path.
|
||||
*/
|
||||
int64_t getLength() const;
|
||||
int64_t polylineLength() const { return getLength(); }
|
||||
|
||||
/*!
|
||||
* Put all junction locations into a polygon object.
|
||||
*
|
||||
* When this path is not closed the returned Polygon should be handled as a polyline, rather than a polygon.
|
||||
*/
|
||||
Polygon toPolygon() const
|
||||
{
|
||||
Polygon ret;
|
||||
for (const ExtrusionJunction &j : junctions)
|
||||
ret.points.emplace_back(j.p);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get the minimal width of this path
|
||||
*/
|
||||
coord_t getMinimalWidth() const;
|
||||
|
||||
/*!
|
||||
* Removes vertices of the ExtrusionLines to make sure that they are not too high
|
||||
* resolution.
|
||||
*
|
||||
* This removes junctions which are connected to line segments that are shorter
|
||||
* than the `smallest_line_segment`, unless that would introduce a deviation
|
||||
* in the contour of more than `allowed_error_distance`.
|
||||
*
|
||||
* Criteria:
|
||||
* 1. Never remove a junction if either of the connected segments is larger than \p smallest_line_segment
|
||||
* 2. Never remove a junction if the distance between that junction and the final resulting polygon would be higher
|
||||
* than \p allowed_error_distance
|
||||
* 3. The direction of segments longer than \p smallest_line_segment always
|
||||
* remains unaltered (but their end points may change if it is connected to
|
||||
* a small segment)
|
||||
* 4. Never remove a junction if it has a distinctively different width than the next junction, as this can
|
||||
* introduce unwanted irregularities on the wall widths.
|
||||
*
|
||||
* Simplify uses a heuristic and doesn't necessarily remove all removable
|
||||
* vertices under the above criteria, but simplify may never violate these
|
||||
* criteria. Unless the segments or the distance is smaller than the
|
||||
* rounding error of 5 micron.
|
||||
*
|
||||
* Vertices which introduce an error of less than 5 microns are removed
|
||||
* anyway, even if the segments are longer than the smallest line segment.
|
||||
* This makes sure that (practically) co-linear line segments are joined into
|
||||
* a single line segment.
|
||||
* \param smallest_line_segment Maximal length of removed line segments.
|
||||
* \param allowed_error_distance If removing a vertex introduces a deviation
|
||||
* from the original path that is more than this distance, the vertex may
|
||||
* not be removed.
|
||||
* \param maximum_extrusion_area_deviation The maximum extrusion area deviation allowed when removing intermediate
|
||||
* junctions from a straight ExtrusionLine
|
||||
*/
|
||||
void simplify(int64_t smallest_line_segment_squared, int64_t allowed_error_distance_squared, int64_t maximum_extrusion_area_deviation);
|
||||
|
||||
/*!
|
||||
* Computes and returns the total area error (in μm²) of the AB and BC segments of an ABC straight ExtrusionLine
|
||||
* when the junction B with a width B.w is removed from the ExtrusionLine. The area changes due to the fact that the
|
||||
* new simplified line AC has a uniform width which equals to the weighted average of the width of the subsegments
|
||||
* (based on their length).
|
||||
*
|
||||
* \param A Start point of the 3-point-straight line
|
||||
* \param B Intermediate point of the 3-point-straight line
|
||||
* \param C End point of the 3-point-straight line
|
||||
* \param weighted_average_width The weighted average of the widths of the two colinear extrusion segments
|
||||
* */
|
||||
static int64_t calculateExtrusionAreaDeviationError(ExtrusionJunction A, ExtrusionJunction B, ExtrusionJunction C, coord_t& weighted_average_width);
|
||||
|
||||
bool is_contour() const;
|
||||
|
||||
double area() const;
|
||||
};
|
||||
|
||||
static inline Slic3r::ThickPolyline to_thick_polyline(const Arachne::ExtrusionLine &line_junctions)
|
||||
{
|
||||
assert(line_junctions.size() >= 2);
|
||||
Slic3r::ThickPolyline out;
|
||||
out.points.emplace_back(line_junctions.front().p);
|
||||
out.width.emplace_back(line_junctions.front().w);
|
||||
out.points.emplace_back(line_junctions[1].p);
|
||||
out.width.emplace_back(line_junctions[1].w);
|
||||
|
||||
auto it_prev = line_junctions.begin() + 1;
|
||||
for (auto it = line_junctions.begin() + 2; it != line_junctions.end(); ++it) {
|
||||
out.points.emplace_back(it->p);
|
||||
out.width.emplace_back(it_prev->w);
|
||||
out.width.emplace_back(it->w);
|
||||
it_prev = it;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static inline Slic3r::ThickPolyline to_thick_polyline(const ClipperLib_Z::Path &path)
|
||||
{
|
||||
assert(path.size() >= 2);
|
||||
Slic3r::ThickPolyline out;
|
||||
out.points.emplace_back(path.front().x(), path.front().y());
|
||||
out.width.emplace_back(path.front().z());
|
||||
out.points.emplace_back(path[1].x(), path[1].y());
|
||||
out.width.emplace_back(path[1].z());
|
||||
|
||||
auto it_prev = path.begin() + 1;
|
||||
for (auto it = path.begin() + 2; it != path.end(); ++it) {
|
||||
out.points.emplace_back(it->x(), it->y());
|
||||
out.width.emplace_back(it_prev->z());
|
||||
out.width.emplace_back(it->z());
|
||||
it_prev = it;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static inline Polygon to_polygon(const ExtrusionLine &line)
|
||||
{
|
||||
Polygon out;
|
||||
assert(line.junctions.size() >= 3);
|
||||
assert(line.junctions.front().p == line.junctions.back().p);
|
||||
out.points.reserve(line.junctions.size() - 1);
|
||||
for (auto it = line.junctions.begin(); it != line.junctions.end() - 1; ++it)
|
||||
out.points.emplace_back(it->p);
|
||||
return out;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static BoundingBox get_extents(const ExtrusionLine &extrusion_line)
|
||||
{
|
||||
BoundingBox bbox;
|
||||
for (const ExtrusionJunction &junction : extrusion_line.junctions)
|
||||
bbox.merge(junction.p);
|
||||
return bbox;
|
||||
}
|
||||
|
||||
static BoundingBox get_extents(const std::vector<ExtrusionLine> &extrusion_lines)
|
||||
{
|
||||
BoundingBox bbox;
|
||||
for (const ExtrusionLine &extrusion_line : extrusion_lines)
|
||||
bbox.merge(get_extents(extrusion_line));
|
||||
return bbox;
|
||||
}
|
||||
|
||||
static BoundingBox get_extents(const std::vector<const ExtrusionLine *> &extrusion_lines)
|
||||
{
|
||||
BoundingBox bbox;
|
||||
for (const ExtrusionLine *extrusion_line : extrusion_lines) {
|
||||
assert(extrusion_line != nullptr);
|
||||
bbox.merge(get_extents(*extrusion_line));
|
||||
}
|
||||
return bbox;
|
||||
}
|
||||
|
||||
static Points to_points(const ExtrusionLine &extrusion_line)
|
||||
{
|
||||
Points points;
|
||||
points.reserve(extrusion_line.junctions.size());
|
||||
for (const ExtrusionJunction &junction : extrusion_line.junctions)
|
||||
points.emplace_back(junction.p);
|
||||
return points;
|
||||
}
|
||||
|
||||
static std::vector<Points> to_points(const std::vector<const ExtrusionLine *> &extrusion_lines)
|
||||
{
|
||||
std::vector<Points> points;
|
||||
for (const ExtrusionLine *extrusion_line : extrusion_lines) {
|
||||
assert(extrusion_line != nullptr);
|
||||
points.emplace_back(to_points(*extrusion_line));
|
||||
}
|
||||
return points;
|
||||
}
|
||||
#endif
|
||||
|
||||
using VariableWidthLines = std::vector<ExtrusionLine>; //<! The ExtrusionLines generated by libArachne
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void extrusion_paths_append(ExtrusionPaths &dst, const ClipperLib_Z::Paths &extrusion_paths, const ExtrusionRole role, const Flow &flow);
|
||||
void extrusion_paths_append(ExtrusionPaths &dst, const Arachne::ExtrusionLine &extrusion, const ExtrusionRole role, const Flow &flow);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // UTILS_EXTRUSION_LINE_H
|
||||
39
src/libslic3r/Arachne/utils/HalfEdge.hpp
Normal file
39
src/libslic3r/Arachne/utils/HalfEdge.hpp
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef UTILS_HALF_EDGE_H
|
||||
#define UTILS_HALF_EDGE_H
|
||||
|
||||
#include <forward_list>
|
||||
#include <optional>
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
template<typename node_data_t, typename edge_data_t, typename derived_node_t, typename derived_edge_t>
|
||||
class HalfEdgeNode;
|
||||
|
||||
|
||||
template<typename node_data_t, typename edge_data_t, typename derived_node_t, typename derived_edge_t>
|
||||
class HalfEdge
|
||||
{
|
||||
using edge_t = derived_edge_t;
|
||||
using node_t = derived_node_t;
|
||||
public:
|
||||
edge_data_t data;
|
||||
edge_t* twin = nullptr;
|
||||
edge_t* next = nullptr;
|
||||
edge_t* prev = nullptr;
|
||||
node_t* from = nullptr;
|
||||
node_t* to = nullptr;
|
||||
HalfEdge(edge_data_t data)
|
||||
: data(data)
|
||||
{}
|
||||
bool operator==(const edge_t& other)
|
||||
{
|
||||
return this == &other;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // UTILS_HALF_EDGE_H
|
||||
29
src/libslic3r/Arachne/utils/HalfEdgeGraph.hpp
Normal file
29
src/libslic3r/Arachne/utils/HalfEdgeGraph.hpp
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef UTILS_HALF_EDGE_GRAPH_H
|
||||
#define UTILS_HALF_EDGE_GRAPH_H
|
||||
|
||||
|
||||
#include <list>
|
||||
#include <cassert>
|
||||
|
||||
|
||||
|
||||
#include "HalfEdge.hpp"
|
||||
#include "HalfEdgeNode.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
template<class node_data_t, class edge_data_t, class derived_node_t, class derived_edge_t> // types of data contained in nodes and edges
|
||||
class HalfEdgeGraph
|
||||
{
|
||||
public:
|
||||
using edge_t = derived_edge_t;
|
||||
using node_t = derived_node_t;
|
||||
std::list<edge_t> edges;
|
||||
std::list<node_t> nodes;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // UTILS_HALF_EDGE_GRAPH_H
|
||||
38
src/libslic3r/Arachne/utils/HalfEdgeNode.hpp
Normal file
38
src/libslic3r/Arachne/utils/HalfEdgeNode.hpp
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef UTILS_HALF_EDGE_NODE_H
|
||||
#define UTILS_HALF_EDGE_NODE_H
|
||||
|
||||
#include <list>
|
||||
|
||||
#include "../../Point.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
template<typename node_data_t, typename edge_data_t, typename derived_node_t, typename derived_edge_t>
|
||||
class HalfEdge;
|
||||
|
||||
template<typename node_data_t, typename edge_data_t, typename derived_node_t, typename derived_edge_t>
|
||||
class HalfEdgeNode
|
||||
{
|
||||
using edge_t = derived_edge_t;
|
||||
using node_t = derived_node_t;
|
||||
public:
|
||||
node_data_t data;
|
||||
Point p;
|
||||
edge_t* incident_edge = nullptr;
|
||||
HalfEdgeNode(node_data_t data, Point p)
|
||||
: data(data)
|
||||
, p(p)
|
||||
{}
|
||||
|
||||
bool operator==(const node_t& other)
|
||||
{
|
||||
return this == &other;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // UTILS_HALF_EDGE_NODE_H
|
||||
180
src/libslic3r/Arachne/utils/PolygonsPointIndex.hpp
Normal file
180
src/libslic3r/Arachne/utils/PolygonsPointIndex.hpp
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
//Copyright (c) 2018 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef UTILS_POLYGONS_POINT_INDEX_H
|
||||
#define UTILS_POLYGONS_POINT_INDEX_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "../../Point.hpp"
|
||||
#include "../../Polygon.hpp"
|
||||
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
// Identity function, used to be able to make templated algorithms where the input is sometimes points, sometimes things that contain or can be converted to points.
|
||||
inline const Point &make_point(const Point &p) { return p; }
|
||||
|
||||
/*!
|
||||
* A class for iterating over the points in one of the polygons in a \ref Polygons object
|
||||
*/
|
||||
template<typename Paths>
|
||||
class PathsPointIndex
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* The polygons into which this index is indexing.
|
||||
*/
|
||||
const Paths* polygons; // (pointer to const polygons)
|
||||
|
||||
unsigned int poly_idx; //!< The index of the polygon in \ref PolygonsPointIndex::polygons
|
||||
|
||||
unsigned int point_idx; //!< The index of the point in the polygon in \ref PolygonsPointIndex::polygons
|
||||
|
||||
/*!
|
||||
* Constructs an empty point index to no polygon.
|
||||
*
|
||||
* This is used as a placeholder for when there is a zero-construction
|
||||
* needed. Since the `polygons` field is const you can't ever make this
|
||||
* initialisation useful.
|
||||
*/
|
||||
PathsPointIndex() : polygons(nullptr), poly_idx(0), point_idx(0) {}
|
||||
|
||||
/*!
|
||||
* Constructs a new point index to a vertex of a polygon.
|
||||
* \param polygons The Polygons instance to which this index points.
|
||||
* \param poly_idx The index of the sub-polygon to point to.
|
||||
* \param point_idx The index of the vertex in the sub-polygon.
|
||||
*/
|
||||
PathsPointIndex(const Paths *polygons, unsigned int poly_idx, unsigned int point_idx) : polygons(polygons), poly_idx(poly_idx), point_idx(point_idx) {}
|
||||
|
||||
/*!
|
||||
* Copy constructor to copy these indices.
|
||||
*/
|
||||
PathsPointIndex(const PathsPointIndex& original) = default;
|
||||
|
||||
Point p() const
|
||||
{
|
||||
if (!polygons)
|
||||
return {0, 0};
|
||||
|
||||
return make_point((*polygons)[poly_idx][point_idx]);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns whether this point is initialised.
|
||||
*/
|
||||
bool initialized() const { return polygons; }
|
||||
|
||||
/*!
|
||||
* Get the polygon to which this PolygonsPointIndex refers
|
||||
*/
|
||||
const Polygon &getPolygon() const { return (*polygons)[poly_idx]; }
|
||||
|
||||
/*!
|
||||
* Test whether two iterators refer to the same polygon in the same polygon list.
|
||||
*
|
||||
* \param other The PolygonsPointIndex to test for equality
|
||||
* \return Wether the right argument refers to the same polygon in the same ListPolygon as the left argument.
|
||||
*/
|
||||
bool operator==(const PathsPointIndex &other) const
|
||||
{
|
||||
return polygons == other.polygons && poly_idx == other.poly_idx && point_idx == other.point_idx;
|
||||
}
|
||||
bool operator!=(const PathsPointIndex &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
bool operator<(const PathsPointIndex &other) const
|
||||
{
|
||||
return this->p() < other.p();
|
||||
}
|
||||
PathsPointIndex &operator=(const PathsPointIndex &other)
|
||||
{
|
||||
polygons = other.polygons;
|
||||
poly_idx = other.poly_idx;
|
||||
point_idx = other.point_idx;
|
||||
return *this;
|
||||
}
|
||||
//! move the iterator forward (and wrap around at the end)
|
||||
PathsPointIndex &operator++()
|
||||
{
|
||||
point_idx = (point_idx + 1) % (*polygons)[poly_idx].size();
|
||||
return *this;
|
||||
}
|
||||
//! move the iterator backward (and wrap around at the beginning)
|
||||
PathsPointIndex &operator--()
|
||||
{
|
||||
if (point_idx == 0)
|
||||
point_idx = (*polygons)[poly_idx].size();
|
||||
point_idx--;
|
||||
return *this;
|
||||
}
|
||||
//! move the iterator forward (and wrap around at the end)
|
||||
PathsPointIndex next() const
|
||||
{
|
||||
PathsPointIndex ret(*this);
|
||||
++ret;
|
||||
return ret;
|
||||
}
|
||||
//! move the iterator backward (and wrap around at the beginning)
|
||||
PathsPointIndex prev() const
|
||||
{
|
||||
PathsPointIndex ret(*this);
|
||||
--ret;
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
using PolygonsPointIndex = PathsPointIndex<Polygons>;
|
||||
|
||||
/*!
|
||||
* Locator to extract a line segment out of a \ref PolygonsPointIndex
|
||||
*/
|
||||
struct PolygonsPointIndexSegmentLocator
|
||||
{
|
||||
std::pair<Point, Point> operator()(const PolygonsPointIndex &val) const
|
||||
{
|
||||
const Polygon &poly = (*val.polygons)[val.poly_idx];
|
||||
Point start = poly[val.point_idx];
|
||||
unsigned int next_point_idx = (val.point_idx + 1) % poly.size();
|
||||
Point end = poly[next_point_idx];
|
||||
return std::pair<Point, Point>(start, end);
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* Locator of a \ref PolygonsPointIndex
|
||||
*/
|
||||
template<typename Paths>
|
||||
struct PathsPointIndexLocator
|
||||
{
|
||||
Point operator()(const PathsPointIndex<Paths>& val) const
|
||||
{
|
||||
return make_point(val.p());
|
||||
}
|
||||
};
|
||||
|
||||
using PolygonsPointIndexLocator = PathsPointIndexLocator<Polygons>;
|
||||
|
||||
}//namespace Slic3r::Arachne
|
||||
|
||||
namespace std
|
||||
{
|
||||
/*!
|
||||
* Hash function for \ref PolygonsPointIndex
|
||||
*/
|
||||
template <>
|
||||
struct hash<Slic3r::Arachne::PolygonsPointIndex>
|
||||
{
|
||||
size_t operator()(const Slic3r::Arachne::PolygonsPointIndex& lpi) const
|
||||
{
|
||||
return Slic3r::PointHash{}(lpi.p());
|
||||
}
|
||||
};
|
||||
}//namespace std
|
||||
|
||||
|
||||
|
||||
#endif//UTILS_POLYGONS_POINT_INDEX_H
|
||||
31
src/libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp
Normal file
31
src/libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef UTILS_POLYGONS_SEGMENT_INDEX_H
|
||||
#define UTILS_POLYGONS_SEGMENT_INDEX_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "PolygonsPointIndex.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
/*!
|
||||
* A class for iterating over the points in one of the polygons in a \ref Polygons object
|
||||
*/
|
||||
class PolygonsSegmentIndex : public PolygonsPointIndex
|
||||
{
|
||||
public:
|
||||
PolygonsSegmentIndex() : PolygonsPointIndex(){};
|
||||
PolygonsSegmentIndex(const Polygons *polygons, unsigned int poly_idx, unsigned int point_idx) : PolygonsPointIndex(polygons, poly_idx, point_idx){};
|
||||
|
||||
Point from() const { return PolygonsPointIndex::p(); }
|
||||
|
||||
Point to() const { return PolygonsSegmentIndex::next().p(); }
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
||||
|
||||
#endif//UTILS_POLYGONS_SEGMENT_INDEX_H
|
||||
42
src/libslic3r/Arachne/utils/PolylineStitcher.cpp
Normal file
42
src/libslic3r/Arachne/utils/PolylineStitcher.cpp
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "PolylineStitcher.hpp"
|
||||
#include "ExtrusionLine.hpp"
|
||||
|
||||
namespace Slic3r::Arachne {
|
||||
|
||||
template<> bool PolylineStitcher<VariableWidthLines, ExtrusionLine, ExtrusionJunction>::canReverse(const PathsPointIndex<VariableWidthLines> &ppi)
|
||||
{
|
||||
if ((*ppi.polygons)[ppi.poly_idx].is_odd)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
template<> bool PolylineStitcher<Polygons, Polygon, Point>::canReverse(const PathsPointIndex<Polygons> &)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
template<> bool PolylineStitcher<VariableWidthLines, ExtrusionLine, ExtrusionJunction>::canConnect(const ExtrusionLine &a, const ExtrusionLine &b)
|
||||
{
|
||||
return a.is_odd == b.is_odd;
|
||||
}
|
||||
|
||||
template<> bool PolylineStitcher<Polygons, Polygon, Point>::canConnect(const Polygon &, const Polygon &)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
template<> bool PolylineStitcher<VariableWidthLines, ExtrusionLine, ExtrusionJunction>::isOdd(const ExtrusionLine &line)
|
||||
{
|
||||
return line.is_odd;
|
||||
}
|
||||
|
||||
template<> bool PolylineStitcher<Polygons, Polygon, Point>::isOdd(const Polygon &)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
234
src/libslic3r/Arachne/utils/PolylineStitcher.hpp
Normal file
234
src/libslic3r/Arachne/utils/PolylineStitcher.hpp
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef UTILS_POLYLINE_STITCHER_H
|
||||
#define UTILS_POLYLINE_STITCHER_H
|
||||
|
||||
#include "SparsePointGrid.hpp"
|
||||
#include "PolygonsPointIndex.hpp"
|
||||
#include "../../Polygon.hpp"
|
||||
#include <unordered_set>
|
||||
#include <cassert>
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
/*!
|
||||
* Class for stitching polylines into longer polylines or into polygons
|
||||
*/
|
||||
template<typename Paths, typename Path, typename Junction>
|
||||
class PolylineStitcher
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* Stitch together the separate \p lines into \p result_lines and if they
|
||||
* can be closed into \p result_polygons.
|
||||
*
|
||||
* Only introduce new segments shorter than \p max_stitch_distance, and
|
||||
* larger than \p snap_distance but always try to take the shortest
|
||||
* connection possible.
|
||||
*
|
||||
* Only stitch polylines into closed polygons if they are larger than 3 *
|
||||
* \p max_stitch_distance, in order to prevent small segments to
|
||||
* accidentally get closed into a polygon.
|
||||
*
|
||||
* \warning Tiny polylines (smaller than 3 * max_stitch_distance) will not
|
||||
* be closed into polygons.
|
||||
*
|
||||
* \note Resulting polylines and polygons are added onto the existing
|
||||
* containers, so you can directly output onto a polygons container with
|
||||
* existing polygons in it. However, you shouldn't call this function with
|
||||
* the same parameter in \p lines as \p result_lines, because that would
|
||||
* duplicate (some of) the polylines.
|
||||
* \param lines The lines to stitch together.
|
||||
* \param result_lines[out] The stitched parts that are not closed polygons
|
||||
* will be stored in here.
|
||||
* \param result_polygons[out] The stitched parts that were closed as
|
||||
* polygons will be stored in here.
|
||||
* \param max_stitch_distance The maximum distance that will be bridged to
|
||||
* connect two lines.
|
||||
* \param snap_distance Points closer than this distance are considered to
|
||||
* be the same point.
|
||||
*/
|
||||
static void stitch(const Paths& lines, Paths& result_lines, Paths& result_polygons, coord_t max_stitch_distance = scaled<coord_t>(0.1), coord_t snap_distance = scaled<coord_t>(0.01))
|
||||
{
|
||||
if (lines.empty())
|
||||
return;
|
||||
|
||||
SparsePointGrid<PathsPointIndex<Paths>, PathsPointIndexLocator<Paths>> grid(max_stitch_distance, lines.size() * 2);
|
||||
|
||||
// populate grid
|
||||
for (size_t line_idx = 0; line_idx < lines.size(); line_idx++)
|
||||
{
|
||||
const auto line = lines[line_idx];
|
||||
grid.insert(PathsPointIndex<Paths>(&lines, line_idx, 0));
|
||||
grid.insert(PathsPointIndex<Paths>(&lines, line_idx, line.size() - 1));
|
||||
}
|
||||
|
||||
std::vector<bool> processed(lines.size(), false);
|
||||
|
||||
for (size_t line_idx = 0; line_idx < lines.size(); line_idx++)
|
||||
{
|
||||
if (processed[line_idx])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
processed[line_idx] = true;
|
||||
const auto line = lines[line_idx];
|
||||
bool should_close = isOdd(line);
|
||||
|
||||
Path chain = line;
|
||||
bool closest_is_closing_polygon = false;
|
||||
for (bool go_in_reverse_direction : { false, true }) // first go in the unreversed direction, to try to prevent the chain.reverse() operation.
|
||||
{ // NOTE: Implementation only works for this order; we currently only re-reverse the chain when it's closed.
|
||||
if (go_in_reverse_direction)
|
||||
{ // try extending chain in the other direction
|
||||
chain.reverse();
|
||||
}
|
||||
int64_t chain_length = chain.polylineLength();
|
||||
|
||||
while (true)
|
||||
{
|
||||
Point from = make_point(chain.back());
|
||||
|
||||
PathsPointIndex<Paths> closest;
|
||||
coord_t closest_distance = std::numeric_limits<coord_t>::max();
|
||||
grid.processNearby(from, max_stitch_distance,
|
||||
std::function<bool (const PathsPointIndex<Paths>&)> (
|
||||
[from, &chain, &closest, &closest_is_closing_polygon, &closest_distance, &processed, &chain_length, go_in_reverse_direction, max_stitch_distance, snap_distance, should_close]
|
||||
(const PathsPointIndex<Paths>& nearby)->bool
|
||||
{
|
||||
bool is_closing_segment = false;
|
||||
coord_t dist = (nearby.p().template cast<int64_t>() - from.template cast<int64_t>()).norm();
|
||||
if (dist > max_stitch_distance)
|
||||
{
|
||||
return true; // keep looking
|
||||
}
|
||||
if ((nearby.p().template cast<int64_t>() - make_point(chain.front()).template cast<int64_t>()).squaredNorm() < snap_distance * snap_distance)
|
||||
{
|
||||
if (chain_length + dist < 3 * max_stitch_distance // prevent closing of small poly, cause it might be able to continue making a larger polyline
|
||||
|| chain.size() <= 2) // don't make 2 vert polygons
|
||||
{
|
||||
return true; // look for a better next line
|
||||
}
|
||||
is_closing_segment = true;
|
||||
if (!should_close)
|
||||
{
|
||||
dist += scaled<coord_t>(0.01); // prefer continuing polyline over closing a polygon; avoids closed zigzags from being printed separately
|
||||
// continue to see if closing segment is also the closest
|
||||
// there might be a segment smaller than [max_stitch_distance] which closes the polygon better
|
||||
}
|
||||
else
|
||||
{
|
||||
dist -= scaled<coord_t>(0.01); //Prefer closing the polygon if it's 100% even lines. Used to create closed contours.
|
||||
//Continue to see if closing segment is also the closest.
|
||||
}
|
||||
}
|
||||
else if (processed[nearby.poly_idx])
|
||||
{ // it was already moved to output
|
||||
return true; // keep looking for a connection
|
||||
}
|
||||
bool nearby_would_be_reversed = nearby.point_idx != 0;
|
||||
nearby_would_be_reversed = nearby_would_be_reversed != go_in_reverse_direction; // flip nearby_would_be_reversed when searching in the reverse direction
|
||||
if (!canReverse(nearby) && nearby_would_be_reversed)
|
||||
{ // connecting the segment would reverse the polygon direction
|
||||
return true; // keep looking for a connection
|
||||
}
|
||||
if (!canConnect(chain, (*nearby.polygons)[nearby.poly_idx]))
|
||||
{
|
||||
return true; // keep looking for a connection
|
||||
}
|
||||
if (dist < closest_distance)
|
||||
{
|
||||
closest_distance = dist;
|
||||
closest = nearby;
|
||||
closest_is_closing_polygon = is_closing_segment;
|
||||
}
|
||||
if (dist < snap_distance)
|
||||
{ // we have found a good enough next line
|
||||
return false; // stop looking for alternatives
|
||||
}
|
||||
return true; // keep processing elements
|
||||
})
|
||||
);
|
||||
|
||||
if (!closest.initialized() // we couldn't find any next line
|
||||
|| closest_is_closing_polygon // we closed the polygon
|
||||
)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
coord_t segment_dist = (make_point(chain.back()).template cast<int64_t>() - closest.p().template cast<int64_t>()).norm();
|
||||
assert(segment_dist <= max_stitch_distance + scaled<coord_t>(0.01));
|
||||
const size_t old_size = chain.size();
|
||||
if (closest.point_idx == 0)
|
||||
{
|
||||
auto start_pos = (*closest.polygons)[closest.poly_idx].begin();
|
||||
if (segment_dist < snap_distance)
|
||||
{
|
||||
++start_pos;
|
||||
}
|
||||
chain.insert(chain.end(), start_pos, (*closest.polygons)[closest.poly_idx].end());
|
||||
}
|
||||
else
|
||||
{
|
||||
auto start_pos = (*closest.polygons)[closest.poly_idx].rbegin();
|
||||
if (segment_dist < snap_distance)
|
||||
{
|
||||
++start_pos;
|
||||
}
|
||||
chain.insert(chain.end(), start_pos, (*closest.polygons)[closest.poly_idx].rend());
|
||||
}
|
||||
for(size_t i = old_size; i < chain.size(); ++i) //Update chain length.
|
||||
{
|
||||
chain_length += (make_point(chain[i]).template cast<int64_t>() - make_point(chain[i - 1]).template cast<int64_t>()).norm();
|
||||
}
|
||||
should_close = should_close & !isOdd((*closest.polygons)[closest.poly_idx]); //If we connect an even to an odd line, we should no longer try to close it.
|
||||
assert( ! processed[closest.poly_idx]);
|
||||
processed[closest.poly_idx] = true;
|
||||
}
|
||||
if (closest_is_closing_polygon)
|
||||
{
|
||||
if (go_in_reverse_direction)
|
||||
{ // re-reverse chain to retain original direction
|
||||
// NOTE: not sure if this code could ever be reached, since if a polygon can be closed that should be already possible in the forward direction
|
||||
chain.reverse();
|
||||
}
|
||||
|
||||
break; // don't consider reverse direction
|
||||
}
|
||||
}
|
||||
if (closest_is_closing_polygon)
|
||||
{
|
||||
result_polygons.emplace_back(chain);
|
||||
}
|
||||
else
|
||||
{
|
||||
PathsPointIndex<Paths> ppi_here(&lines, line_idx, 0);
|
||||
if ( ! canReverse(ppi_here))
|
||||
{ // Since closest_is_closing_polygon is false we went through the second iterations of the for-loop, where go_in_reverse_direction is true
|
||||
// the polyline isn't allowed to be reversed, so we re-reverse it.
|
||||
chain.reverse();
|
||||
}
|
||||
result_lines.emplace_back(chain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Whether a polyline is allowed to be reversed. (Not true for wall polylines which are not odd)
|
||||
*/
|
||||
static bool canReverse(const PathsPointIndex<Paths> &polyline);
|
||||
|
||||
/*!
|
||||
* Whether two paths are allowed to be connected.
|
||||
* (Not true for an odd and an even wall.)
|
||||
*/
|
||||
static bool canConnect(const Path &a, const Path &b);
|
||||
|
||||
static bool isOdd(const Path &line);
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // UTILS_POLYLINE_STITCHER_H
|
||||
133
src/libslic3r/Arachne/utils/SparseGrid.hpp
Normal file
133
src/libslic3r/Arachne/utils/SparseGrid.hpp
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
//Copyright (c) 2016 Scott Lenser
|
||||
//Copyright (c) 2018 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef UTILS_SPARSE_GRID_H
|
||||
#define UTILS_SPARSE_GRID_H
|
||||
|
||||
#include <cassert>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
#include "../../Point.hpp"
|
||||
#include "SquareGrid.hpp"
|
||||
|
||||
namespace Slic3r::Arachne {
|
||||
|
||||
/*! \brief Sparse grid which can locate spatially nearby elements efficiently.
|
||||
*
|
||||
* \note This is an abstract template class which doesn't have any functions to insert elements.
|
||||
* \see SparsePointGrid
|
||||
*
|
||||
* \tparam ElemT The element type to store.
|
||||
*/
|
||||
template<class ElemT> class SparseGrid : public SquareGrid
|
||||
{
|
||||
public:
|
||||
using Elem = ElemT;
|
||||
|
||||
using GridPoint = SquareGrid::GridPoint;
|
||||
using grid_coord_t = SquareGrid::grid_coord_t;
|
||||
using GridMap = std::unordered_multimap<GridPoint, Elem, PointHash>;
|
||||
|
||||
using iterator = typename GridMap::iterator;
|
||||
using const_iterator = typename GridMap::const_iterator;
|
||||
|
||||
/*! \brief Constructs a sparse grid with the specified cell size.
|
||||
*
|
||||
* \param[in] cell_size The size to use for a cell (square) in the grid.
|
||||
* Typical values would be around 0.5-2x of expected query radius.
|
||||
* \param[in] elem_reserve Number of elements to research space for.
|
||||
* \param[in] max_load_factor Maximum average load factor before rehashing.
|
||||
*/
|
||||
SparseGrid(coord_t cell_size, size_t elem_reserve=0U, float max_load_factor=1.0f);
|
||||
|
||||
iterator begin() { return m_grid.begin(); }
|
||||
iterator end() { return m_grid.end(); }
|
||||
const_iterator begin() const { return m_grid.begin(); }
|
||||
const_iterator end() const { return m_grid.end(); }
|
||||
|
||||
/*! \brief Returns all data within radius of query_pt.
|
||||
*
|
||||
* Finds all elements with location within radius of \p query_pt. May
|
||||
* return additional elements that are beyond radius.
|
||||
*
|
||||
* Average running time is a*(1 + 2 * radius / cell_size)**2 +
|
||||
* b*cnt where a and b are proportionality constance and cnt is
|
||||
* the number of returned items. The search will return items in
|
||||
* an area of (2*radius + cell_size)**2 on average. The max range
|
||||
* of an item from the query_point is radius + cell_size.
|
||||
*
|
||||
* \param[in] query_pt The point to search around.
|
||||
* \param[in] radius The search radius.
|
||||
* \return Vector of elements found
|
||||
*/
|
||||
std::vector<Elem> getNearby(const Point &query_pt, coord_t radius) const;
|
||||
|
||||
/*! \brief Process elements from cells that might contain sought after points.
|
||||
*
|
||||
* Processes elements from cell that might have elements within \p
|
||||
* radius of \p query_pt. Processes all elements that are within
|
||||
* radius of query_pt. May process elements that are up to radius +
|
||||
* cell_size from query_pt.
|
||||
*
|
||||
* \param[in] query_pt The point to search around.
|
||||
* \param[in] radius The search radius.
|
||||
* \param[in] process_func Processes each element. process_func(elem) is
|
||||
* called for each element in the cell. Processing stops if function returns false.
|
||||
* \return Whether we need to continue processing after this function
|
||||
*/
|
||||
bool processNearby(const Point &query_pt, coord_t radius, const std::function<bool(const ElemT &)> &process_func) const;
|
||||
|
||||
protected:
|
||||
/*! \brief Process elements from the cell indicated by \p grid_pt.
|
||||
*
|
||||
* \param[in] grid_pt The grid coordinates of the cell.
|
||||
* \param[in] process_func Processes each element. process_func(elem) is
|
||||
* called for each element in the cell. Processing stops if function returns false.
|
||||
* \return Whether we need to continue processing a next cell.
|
||||
*/
|
||||
bool processFromCell(const GridPoint &grid_pt, const std::function<bool(const Elem &)> &process_func) const;
|
||||
|
||||
/*! \brief Map from grid locations (GridPoint) to elements (Elem). */
|
||||
GridMap m_grid;
|
||||
};
|
||||
|
||||
template<class ElemT> SparseGrid<ElemT>::SparseGrid(coord_t cell_size, size_t elem_reserve, float max_load_factor) : SquareGrid(cell_size)
|
||||
{
|
||||
// Must be before the reserve call.
|
||||
m_grid.max_load_factor(max_load_factor);
|
||||
if (elem_reserve != 0U)
|
||||
m_grid.reserve(elem_reserve);
|
||||
}
|
||||
|
||||
template<class ElemT> bool SparseGrid<ElemT>::processFromCell(const GridPoint &grid_pt, const std::function<bool(const Elem &)> &process_func) const
|
||||
{
|
||||
auto grid_range = m_grid.equal_range(grid_pt);
|
||||
for (auto iter = grid_range.first; iter != grid_range.second; ++iter)
|
||||
if (!process_func(iter->second))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<class ElemT>
|
||||
bool SparseGrid<ElemT>::processNearby(const Point &query_pt, coord_t radius, const std::function<bool(const Elem &)> &process_func) const
|
||||
{
|
||||
return SquareGrid::processNearby(query_pt, radius, [&process_func, this](const GridPoint &grid_pt) { return processFromCell(grid_pt, process_func); });
|
||||
}
|
||||
|
||||
template<class ElemT> std::vector<typename SparseGrid<ElemT>::Elem> SparseGrid<ElemT>::getNearby(const Point &query_pt, coord_t radius) const
|
||||
{
|
||||
std::vector<Elem> ret;
|
||||
const std::function<bool(const Elem &)> process_func = [&ret](const Elem &elem) {
|
||||
ret.push_back(elem);
|
||||
return true;
|
||||
};
|
||||
processNearby(query_pt, radius, process_func);
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
||||
#endif // UTILS_SPARSE_GRID_H
|
||||
77
src/libslic3r/Arachne/utils/SparseLineGrid.hpp
Normal file
77
src/libslic3r/Arachne/utils/SparseLineGrid.hpp
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
//Copyright (c) 2018 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
|
||||
#ifndef UTILS_SPARSE_LINE_GRID_H
|
||||
#define UTILS_SPARSE_LINE_GRID_H
|
||||
|
||||
#include <cassert>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
#include "SparseGrid.hpp"
|
||||
|
||||
namespace Slic3r::Arachne {
|
||||
|
||||
/*! \brief Sparse grid which can locate spatially nearby elements efficiently.
|
||||
*
|
||||
* \tparam ElemT The element type to store.
|
||||
* \tparam Locator The functor to get the start and end locations from ElemT.
|
||||
* must have: std::pair<Point, Point> operator()(const ElemT &elem) const
|
||||
* which returns the location associated with val.
|
||||
*/
|
||||
template<class ElemT, class Locator> class SparseLineGrid : public SparseGrid<ElemT>
|
||||
{
|
||||
public:
|
||||
using Elem = ElemT;
|
||||
|
||||
/*! \brief Constructs a sparse grid with the specified cell size.
|
||||
*
|
||||
* \param[in] cell_size The size to use for a cell (square) in the grid.
|
||||
* Typical values would be around 0.5-2x of expected query radius.
|
||||
* \param[in] elem_reserve Number of elements to research space for.
|
||||
* \param[in] max_load_factor Maximum average load factor before rehashing.
|
||||
*/
|
||||
SparseLineGrid(coord_t cell_size, size_t elem_reserve = 0U, float max_load_factor = 1.0f);
|
||||
|
||||
/*! \brief Inserts elem into the sparse grid.
|
||||
*
|
||||
* \param[in] elem The element to be inserted.
|
||||
*/
|
||||
void insert(const Elem &elem);
|
||||
|
||||
protected:
|
||||
using GridPoint = typename SparseGrid<ElemT>::GridPoint;
|
||||
|
||||
/*! \brief Accessor for getting locations from elements. */
|
||||
Locator m_locator;
|
||||
};
|
||||
|
||||
template<class ElemT, class Locator>
|
||||
SparseLineGrid<ElemT, Locator>::SparseLineGrid(coord_t cell_size, size_t elem_reserve, float max_load_factor)
|
||||
: SparseGrid<ElemT>(cell_size, elem_reserve, max_load_factor) {}
|
||||
|
||||
template<class ElemT, class Locator> void SparseLineGrid<ElemT, Locator>::insert(const Elem &elem)
|
||||
{
|
||||
const std::pair<Point, Point> line = m_locator(elem);
|
||||
using GridMap = std::unordered_multimap<GridPoint, Elem, PointHash>;
|
||||
// below is a workaround for the fact that lambda functions cannot access private or protected members
|
||||
// first we define a lambda which works on any GridMap and then we bind it to the actual protected GridMap of the parent class
|
||||
std::function<bool(GridMap *, const GridPoint)> process_cell_func_ = [&elem](GridMap *m_grid, const GridPoint grid_loc) {
|
||||
m_grid->emplace(grid_loc, elem);
|
||||
return true;
|
||||
};
|
||||
using namespace std::placeholders; // for _1, _2, _3...
|
||||
GridMap *m_grid = &(this->m_grid);
|
||||
std::function<bool(const GridPoint)> process_cell_func(std::bind(process_cell_func_, m_grid, _1));
|
||||
|
||||
SparseGrid<ElemT>::processLineCells(line, process_cell_func);
|
||||
}
|
||||
|
||||
#undef SGI_TEMPLATE
|
||||
#undef SGI_THIS
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
||||
#endif // UTILS_SPARSE_LINE_GRID_H
|
||||
90
src/libslic3r/Arachne/utils/SparsePointGrid.hpp
Normal file
90
src/libslic3r/Arachne/utils/SparsePointGrid.hpp
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
// Copyright (c) 2016 Scott Lenser
|
||||
// Copyright (c) 2020 Ultimaker B.V.
|
||||
// CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef UTILS_SPARSE_POINT_GRID_H
|
||||
#define UTILS_SPARSE_POINT_GRID_H
|
||||
|
||||
#include <cassert>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "SparseGrid.hpp"
|
||||
|
||||
namespace Slic3r::Arachne {
|
||||
|
||||
/*! \brief Sparse grid which can locate spatially nearby elements efficiently.
|
||||
*
|
||||
* \tparam ElemT The element type to store.
|
||||
* \tparam Locator The functor to get the location from ElemT. Locator
|
||||
* must have: Point operator()(const ElemT &elem) const
|
||||
* which returns the location associated with val.
|
||||
*/
|
||||
template<class ElemT, class Locator> class SparsePointGrid : public SparseGrid<ElemT>
|
||||
{
|
||||
public:
|
||||
using Elem = ElemT;
|
||||
|
||||
/*! \brief Constructs a sparse grid with the specified cell size.
|
||||
*
|
||||
* \param[in] cell_size The size to use for a cell (square) in the grid.
|
||||
* Typical values would be around 0.5-2x of expected query radius.
|
||||
* \param[in] elem_reserve Number of elements to research space for.
|
||||
* \param[in] max_load_factor Maximum average load factor before rehashing.
|
||||
*/
|
||||
SparsePointGrid(coord_t cell_size, size_t elem_reserve = 0U, float max_load_factor = 1.0f);
|
||||
|
||||
/*! \brief Inserts elem into the sparse grid.
|
||||
*
|
||||
* \param[in] elem The element to be inserted.
|
||||
*/
|
||||
void insert(const Elem &elem);
|
||||
|
||||
/*!
|
||||
* Get just any element that's within a certain radius of a point.
|
||||
*
|
||||
* Rather than giving a vector of nearby elements, this function just gives
|
||||
* a single element, any element, in no particular order.
|
||||
* \param query_pt The point to query for an object nearby.
|
||||
* \param radius The radius of what is considered "nearby".
|
||||
*/
|
||||
const ElemT *getAnyNearby(const Point &query_pt, coord_t radius);
|
||||
|
||||
protected:
|
||||
using GridPoint = typename SparseGrid<ElemT>::GridPoint;
|
||||
|
||||
/*! \brief Accessor for getting locations from elements. */
|
||||
Locator m_locator;
|
||||
};
|
||||
|
||||
template<class ElemT, class Locator>
|
||||
SparsePointGrid<ElemT, Locator>::SparsePointGrid(coord_t cell_size, size_t elem_reserve, float max_load_factor) : SparseGrid<ElemT>(cell_size, elem_reserve, max_load_factor) {}
|
||||
|
||||
template<class ElemT, class Locator>
|
||||
void SparsePointGrid<ElemT, Locator>::insert(const Elem &elem)
|
||||
{
|
||||
Point loc = m_locator(elem);
|
||||
GridPoint grid_loc = SparseGrid<ElemT>::toGridPoint(loc.template cast<int64_t>());
|
||||
|
||||
SparseGrid<ElemT>::m_grid.emplace(grid_loc, elem);
|
||||
}
|
||||
|
||||
template<class ElemT, class Locator>
|
||||
const ElemT *SparsePointGrid<ElemT, Locator>::getAnyNearby(const Point &query_pt, coord_t radius)
|
||||
{
|
||||
const ElemT *ret = nullptr;
|
||||
const std::function<bool(const ElemT &)> &process_func = [&ret, query_pt, radius, this](const ElemT &maybe_nearby) {
|
||||
if (shorter_then(m_locator(maybe_nearby) - query_pt, radius)) {
|
||||
ret = &maybe_nearby;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
SparseGrid<ElemT>::processNearby(query_pt, radius, process_func);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
||||
#endif // UTILS_SPARSE_POINT_GRID_H
|
||||
147
src/libslic3r/Arachne/utils/SquareGrid.cpp
Normal file
147
src/libslic3r/Arachne/utils/SquareGrid.cpp
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
//Copyright (c) 2021 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "SquareGrid.hpp"
|
||||
#include "../../Point.hpp"
|
||||
|
||||
using namespace Slic3r::Arachne;
|
||||
|
||||
|
||||
SquareGrid::SquareGrid(coord_t cell_size) : cell_size(cell_size)
|
||||
{
|
||||
assert(cell_size > 0U);
|
||||
}
|
||||
|
||||
|
||||
SquareGrid::GridPoint SquareGrid::toGridPoint(const Vec2i64 &point) const
|
||||
{
|
||||
return Point(toGridCoord(point.x()), toGridCoord(point.y()));
|
||||
}
|
||||
|
||||
|
||||
SquareGrid::grid_coord_t SquareGrid::toGridCoord(const int64_t &coord) const
|
||||
{
|
||||
// This mapping via truncation results in the cells with
|
||||
// GridPoint.x==0 being twice as large and similarly for
|
||||
// GridPoint.y==0. This doesn't cause any incorrect behavior,
|
||||
// just changes the running time slightly. The change in running
|
||||
// time from this is probably not worth doing a proper floor
|
||||
// operation.
|
||||
return coord / cell_size;
|
||||
}
|
||||
|
||||
coord_t SquareGrid::toLowerCoord(const grid_coord_t& grid_coord) const
|
||||
{
|
||||
// This mapping via truncation results in the cells with
|
||||
// GridPoint.x==0 being twice as large and similarly for
|
||||
// GridPoint.y==0. This doesn't cause any incorrect behavior,
|
||||
// just changes the running time slightly. The change in running
|
||||
// time from this is probably not worth doing a proper floor
|
||||
// operation.
|
||||
return grid_coord * cell_size;
|
||||
}
|
||||
|
||||
|
||||
bool SquareGrid::processLineCells(const std::pair<Point, Point> line, const std::function<bool (GridPoint)>& process_cell_func)
|
||||
{
|
||||
return static_cast<const SquareGrid*>(this)->processLineCells(line, process_cell_func);
|
||||
}
|
||||
|
||||
|
||||
bool SquareGrid::processLineCells(const std::pair<Point, Point> line, const std::function<bool (GridPoint)>& process_cell_func) const
|
||||
{
|
||||
Point start = line.first;
|
||||
Point end = line.second;
|
||||
if (end.x() < start.x())
|
||||
{ // make sure X increases between start and end
|
||||
std::swap(start, end);
|
||||
}
|
||||
|
||||
const GridPoint start_cell = toGridPoint(start.cast<int64_t>());
|
||||
const GridPoint end_cell = toGridPoint(end.cast<int64_t>());
|
||||
const int64_t y_diff = int64_t(end.y() - start.y());
|
||||
const grid_coord_t y_dir = nonzeroSign(y_diff);
|
||||
|
||||
/* This line drawing algorithm iterates over the range of Y coordinates, and
|
||||
for each Y coordinate computes the range of X coordinates crossed in one
|
||||
unit of Y. These ranges are rounded to be inclusive, so effectively this
|
||||
creates a "fat" line, marking more cells than a strict one-cell-wide path.*/
|
||||
grid_coord_t x_cell_start = start_cell.x();
|
||||
for (grid_coord_t cell_y = start_cell.y(); cell_y * y_dir <= end_cell.y() * y_dir; cell_y += y_dir)
|
||||
{ // for all Y from start to end
|
||||
// nearest y coordinate of the cells in the next row
|
||||
const coord_t nearest_next_y = toLowerCoord(cell_y + ((nonzeroSign(cell_y) == y_dir || cell_y == 0) ? y_dir : coord_t(0)));
|
||||
grid_coord_t x_cell_end; // the X coord of the last cell to include from this row
|
||||
if (y_diff == 0)
|
||||
{
|
||||
x_cell_end = end_cell.x();
|
||||
}
|
||||
else
|
||||
{
|
||||
const int64_t area = int64_t(end.x() - start.x()) * int64_t(nearest_next_y - start.y());
|
||||
// corresponding_x: the x coordinate corresponding to nearest_next_y
|
||||
int64_t corresponding_x = int64_t(start.x()) + area / y_diff;
|
||||
x_cell_end = toGridCoord(corresponding_x + ((corresponding_x < 0) && ((area % y_diff) != 0)));
|
||||
if (x_cell_end < start_cell.x())
|
||||
{ // process at least one cell!
|
||||
x_cell_end = x_cell_start;
|
||||
}
|
||||
}
|
||||
|
||||
for (grid_coord_t cell_x = x_cell_start; cell_x <= x_cell_end; ++cell_x)
|
||||
{
|
||||
GridPoint grid_loc(cell_x, cell_y);
|
||||
if (! process_cell_func(grid_loc))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (grid_loc == end_cell)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// TODO: this causes at least a one cell overlap for each row, which
|
||||
// includes extra cells when crossing precisely on the corners
|
||||
// where positive slope where x > 0 and negative slope where x < 0
|
||||
x_cell_start = x_cell_end;
|
||||
}
|
||||
assert(false && "We should have returned already before here!");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SquareGrid::processNearby
|
||||
(
|
||||
const Point &query_pt,
|
||||
coord_t radius,
|
||||
const std::function<bool (const GridPoint&)>& process_func
|
||||
) const
|
||||
{
|
||||
const Point min_loc(query_pt.x() - radius, query_pt.y() - radius);
|
||||
const Point max_loc(query_pt.x() + radius, query_pt.y() + radius);
|
||||
|
||||
GridPoint min_grid = toGridPoint(min_loc.cast<int64_t>());
|
||||
GridPoint max_grid = toGridPoint(max_loc.cast<int64_t>());
|
||||
|
||||
for (coord_t grid_y = min_grid.y(); grid_y <= max_grid.y(); ++grid_y)
|
||||
{
|
||||
for (coord_t grid_x = min_grid.x(); grid_x <= max_grid.x(); ++grid_x)
|
||||
{
|
||||
GridPoint grid_pt(grid_x,grid_y);
|
||||
if (!process_func(grid_pt))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
SquareGrid::grid_coord_t SquareGrid::nonzeroSign(const grid_coord_t z) const
|
||||
{
|
||||
return (z >= 0) - (z < 0);
|
||||
}
|
||||
|
||||
coord_t SquareGrid::getCellSize() const
|
||||
{
|
||||
return cell_size;
|
||||
}
|
||||
110
src/libslic3r/Arachne/utils/SquareGrid.hpp
Normal file
110
src/libslic3r/Arachne/utils/SquareGrid.hpp
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
//Copyright (c) 2021 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef UTILS_SQUARE_GRID_H
|
||||
#define UTILS_SQUARE_GRID_H
|
||||
|
||||
#include "../../Point.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
namespace Slic3r::Arachne {
|
||||
|
||||
/*!
|
||||
* Helper class to calculate coordinates on a square grid, and providing some
|
||||
* utility functions to process grids.
|
||||
*
|
||||
* Doesn't contain any data, except cell size. The purpose is only to
|
||||
* automatically generate coordinates on a grid, and automatically feed them to
|
||||
* functions.
|
||||
* The grid is theoretically infinite (bar integer limits).
|
||||
*/
|
||||
class SquareGrid
|
||||
{
|
||||
public:
|
||||
/*! \brief Constructs a grid with the specified cell size.
|
||||
* \param[in] cell_size The size to use for a cell (square) in the grid.
|
||||
*/
|
||||
SquareGrid(const coord_t cell_size);
|
||||
|
||||
/*!
|
||||
* Get the cell size this grid was created for.
|
||||
*/
|
||||
coord_t getCellSize() const;
|
||||
|
||||
using GridPoint = Point;
|
||||
using grid_coord_t = coord_t;
|
||||
|
||||
/*! \brief Process cells along a line indicated by \p line.
|
||||
*
|
||||
* \param line The line along which to process cells.
|
||||
* \param process_func Processes each cell. ``process_func(elem)`` is called
|
||||
* for each cell. Processing stops if function returns false.
|
||||
* \return Whether we need to continue processing after this function.
|
||||
*/
|
||||
bool processLineCells(const std::pair<Point, Point> line, const std::function<bool (GridPoint)>& process_cell_func);
|
||||
|
||||
/*! \brief Process cells along a line indicated by \p line.
|
||||
*
|
||||
* \param line The line along which to process cells
|
||||
* \param process_func Processes each cell. ``process_func(elem)`` is called
|
||||
* for each cell. Processing stops if function returns false.
|
||||
* \return Whether we need to continue processing after this function.
|
||||
*/
|
||||
bool processLineCells(const std::pair<Point, Point> line, const std::function<bool (GridPoint)>& process_cell_func) const;
|
||||
|
||||
/*! \brief Process cells that might contain sought after points.
|
||||
*
|
||||
* Processes cells that might be within a square with twice \p radius as
|
||||
* width, centered around \p query_pt.
|
||||
* May process elements that are up to radius + cell_size from query_pt.
|
||||
* \param query_pt The point to search around.
|
||||
* \param radius The search radius.
|
||||
* \param process_func Processes each cell. ``process_func(loc)`` is called
|
||||
* for each cell coord within range. Processing stops if function returns
|
||||
* ``false``.
|
||||
* \return Whether we need to continue processing after this function.
|
||||
*/
|
||||
bool processNearby(const Point &query_pt, coord_t radius, const std::function<bool(const GridPoint &)> &process_func) const;
|
||||
|
||||
/*! \brief Compute the grid coordinates of a point.
|
||||
* \param point The actual location.
|
||||
* \return The grid coordinates that correspond to \p point.
|
||||
*/
|
||||
GridPoint toGridPoint(const Vec2i64 &point) const;
|
||||
|
||||
/*! \brief Compute the grid coordinate of a real space coordinate.
|
||||
* \param coord The actual location.
|
||||
* \return The grid coordinate that corresponds to \p coord.
|
||||
*/
|
||||
grid_coord_t toGridCoord(const int64_t &coord) const;
|
||||
|
||||
/*! \brief Compute the lowest coord in a grid cell.
|
||||
* The lowest point is the point in the grid cell closest to the origin.
|
||||
*
|
||||
* \param grid_coord The grid coordinate.
|
||||
* \return The print space coordinate that corresponds to \p grid_coord.
|
||||
*/
|
||||
coord_t toLowerCoord(const grid_coord_t &grid_coord) const;
|
||||
|
||||
protected:
|
||||
/*! \brief The cell (square) size. */
|
||||
coord_t cell_size;
|
||||
|
||||
/*!
|
||||
* Compute the sign of a number.
|
||||
*
|
||||
* The number 0 will result in a positive sign (1).
|
||||
* \param z The number to find the sign of.
|
||||
* \return 1 if the number is positive or 0, or -1 if the number is
|
||||
* negative.
|
||||
*/
|
||||
grid_coord_t nonzeroSign(grid_coord_t z) const;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
||||
#endif //UTILS_SQUARE_GRID_H
|
||||
250
src/libslic3r/Arachne/utils/VoronoiUtils.cpp
Normal file
250
src/libslic3r/Arachne/utils/VoronoiUtils.cpp
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
//Copyright (c) 2021 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include <stack>
|
||||
#include <optional>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include "linearAlg2D.hpp"
|
||||
#include "VoronoiUtils.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
Vec2i64 VoronoiUtils::p(const vd_t::vertex_type *node)
|
||||
{
|
||||
const double x = node->x();
|
||||
const double y = node->y();
|
||||
assert(x <= double(std::numeric_limits<int64_t>::max()) && x >= std::numeric_limits<int64_t>::lowest());
|
||||
assert(y <= double(std::numeric_limits<int64_t>::max()) && y >= std::numeric_limits<int64_t>::lowest());
|
||||
return {int64_t(x + 0.5 - (x < 0)), int64_t(y + 0.5 - (y < 0))}; // Round to the nearest integer coordinates.
|
||||
}
|
||||
|
||||
Point VoronoiUtils::getSourcePoint(const vd_t::cell_type& cell, const std::vector<Segment>& segments)
|
||||
{
|
||||
assert(cell.contains_point());
|
||||
if(!cell.contains_point())
|
||||
BOOST_LOG_TRIVIAL(debug) << "Voronoi cell doesn't contain a source point!";
|
||||
|
||||
switch (cell.source_category()) {
|
||||
case boost::polygon::SOURCE_CATEGORY_SINGLE_POINT:
|
||||
assert(false && "Voronoi diagram is always constructed using segments, so cell.source_category() shouldn't be SOURCE_CATEGORY_SINGLE_POINT!\n");
|
||||
BOOST_LOG_TRIVIAL(error) << "Voronoi diagram is always constructed using segments, so cell.source_category() shouldn't be SOURCE_CATEGORY_SINGLE_POINT!";
|
||||
break;
|
||||
case boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT:
|
||||
assert(cell.source_index() < segments.size());
|
||||
return segments[cell.source_index()].to();
|
||||
break;
|
||||
case boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT:
|
||||
assert(cell.source_index() < segments.size());
|
||||
return segments[cell.source_index()].from();
|
||||
break;
|
||||
default:
|
||||
assert(false && "getSourcePoint should only be called on point cells!\n");
|
||||
break;
|
||||
}
|
||||
|
||||
assert(false && "cell.source_category() is equal to an invalid value!\n");
|
||||
BOOST_LOG_TRIVIAL(error) << "cell.source_category() is equal to an invalid value!";
|
||||
return {};
|
||||
}
|
||||
|
||||
PolygonsPointIndex VoronoiUtils::getSourcePointIndex(const vd_t::cell_type& cell, const std::vector<Segment>& segments)
|
||||
{
|
||||
assert(cell.contains_point());
|
||||
if(!cell.contains_point())
|
||||
BOOST_LOG_TRIVIAL(debug) << "Voronoi cell doesn't contain a source point!";
|
||||
|
||||
assert(cell.source_category() != boost::polygon::SOURCE_CATEGORY_SINGLE_POINT);
|
||||
switch (cell.source_category()) {
|
||||
case boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT: {
|
||||
assert(cell.source_index() < segments.size());
|
||||
PolygonsPointIndex ret = segments[cell.source_index()];
|
||||
++ret;
|
||||
return ret;
|
||||
break;
|
||||
}
|
||||
case boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT: {
|
||||
assert(cell.source_index() < segments.size());
|
||||
return segments[cell.source_index()];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assert(false && "getSourcePoint should only be called on point cells!\n");
|
||||
break;
|
||||
}
|
||||
PolygonsPointIndex ret = segments[cell.source_index()];
|
||||
return ++ret;
|
||||
}
|
||||
|
||||
const VoronoiUtils::Segment &VoronoiUtils::getSourceSegment(const vd_t::cell_type &cell, const std::vector<Segment> &segments)
|
||||
{
|
||||
assert(cell.contains_segment());
|
||||
if (!cell.contains_segment())
|
||||
BOOST_LOG_TRIVIAL(debug) << "Voronoi cell doesn't contain a source segment!";
|
||||
|
||||
return segments[cell.source_index()];
|
||||
}
|
||||
|
||||
class PointMatrix
|
||||
{
|
||||
public:
|
||||
double matrix[4];
|
||||
|
||||
PointMatrix()
|
||||
{
|
||||
matrix[0] = 1;
|
||||
matrix[1] = 0;
|
||||
matrix[2] = 0;
|
||||
matrix[3] = 1;
|
||||
}
|
||||
|
||||
PointMatrix(double rotation)
|
||||
{
|
||||
rotation = rotation / 180 * M_PI;
|
||||
matrix[0] = cos(rotation);
|
||||
matrix[1] = -sin(rotation);
|
||||
matrix[2] = -matrix[1];
|
||||
matrix[3] = matrix[0];
|
||||
}
|
||||
|
||||
PointMatrix(const Point p)
|
||||
{
|
||||
matrix[0] = p.x();
|
||||
matrix[1] = p.y();
|
||||
double f = sqrt((matrix[0] * matrix[0]) + (matrix[1] * matrix[1]));
|
||||
matrix[0] /= f;
|
||||
matrix[1] /= f;
|
||||
matrix[2] = -matrix[1];
|
||||
matrix[3] = matrix[0];
|
||||
}
|
||||
|
||||
static PointMatrix scale(double s)
|
||||
{
|
||||
PointMatrix ret;
|
||||
ret.matrix[0] = s;
|
||||
ret.matrix[3] = s;
|
||||
return ret;
|
||||
}
|
||||
|
||||
Point apply(const Point p) const
|
||||
{
|
||||
return Point(coord_t(p.x() * matrix[0] + p.y() * matrix[1]), coord_t(p.x() * matrix[2] + p.y() * matrix[3]));
|
||||
}
|
||||
|
||||
Point unapply(const Point p) const
|
||||
{
|
||||
return Point(coord_t(p.x() * matrix[0] + p.y() * matrix[2]), coord_t(p.x() * matrix[1] + p.y() * matrix[3]));
|
||||
}
|
||||
};
|
||||
std::vector<Point> VoronoiUtils::discretizeParabola(const Point& p, const Segment& segment, Point s, Point e, coord_t approximate_step_size, float transitioning_angle)
|
||||
{
|
||||
std::vector<Point> discretized;
|
||||
// x is distance of point projected on the segment ab
|
||||
// xx is point projected on the segment ab
|
||||
const Point a = segment.from();
|
||||
const Point b = segment.to();
|
||||
const Point ab = b - a;
|
||||
const Point as = s - a;
|
||||
const Point ae = e - a;
|
||||
const coord_t ab_size = ab.cast<int64_t>().norm();
|
||||
const coord_t sx = as.cast<int64_t>().dot(ab.cast<int64_t>()) / ab_size;
|
||||
const coord_t ex = ae.cast<int64_t>().dot(ab.cast<int64_t>()) / ab_size;
|
||||
const coord_t sxex = ex - sx;
|
||||
|
||||
assert((as.cast<int64_t>().dot(ab.cast<int64_t>()) / int64_t(ab_size)) <= std::numeric_limits<coord_t>::max());
|
||||
assert((ae.cast<int64_t>().dot(ab.cast<int64_t>()) / int64_t(ab_size)) <= std::numeric_limits<coord_t>::max());
|
||||
|
||||
const Point ap = p - a;
|
||||
const coord_t px = ap.cast<int64_t>().dot(ab.cast<int64_t>()) / ab_size;
|
||||
|
||||
assert((ap.cast<int64_t>().dot(ab.cast<int64_t>()) / int64_t(ab_size)) <= std::numeric_limits<coord_t>::max());
|
||||
|
||||
Point pxx;
|
||||
Line(a, b).distance_to_infinite_squared(p, &pxx);
|
||||
const Point ppxx = pxx - p;
|
||||
const coord_t d = ppxx.cast<int64_t>().norm();
|
||||
const PointMatrix rot = PointMatrix(ppxx.rotate_90_degree_ccw());
|
||||
|
||||
if (d == 0)
|
||||
{
|
||||
discretized.emplace_back(s);
|
||||
discretized.emplace_back(e);
|
||||
return discretized;
|
||||
}
|
||||
|
||||
const float marking_bound = atan(transitioning_angle * 0.5);
|
||||
int64_t msx = - marking_bound * int64_t(d); // projected marking_start
|
||||
int64_t mex = marking_bound * int64_t(d); // projected marking_end
|
||||
|
||||
assert(msx <= std::numeric_limits<coord_t>::max());
|
||||
assert(double(msx) * double(msx) <= double(std::numeric_limits<int64_t>::max()));
|
||||
assert(mex <= std::numeric_limits<coord_t>::max());
|
||||
assert(double(msx) * double(msx) / double(2 * d) + double(d / 2) <= std::numeric_limits<coord_t>::max());
|
||||
|
||||
const coord_t marking_start_end_h = msx * msx / (2 * d) + d / 2;
|
||||
Point marking_start = rot.unapply(Point(coord_t(msx), marking_start_end_h)) + pxx;
|
||||
Point marking_end = rot.unapply(Point(coord_t(mex), marking_start_end_h)) + pxx;
|
||||
const int dir = (sx > ex) ? -1 : 1;
|
||||
if (dir < 0)
|
||||
{
|
||||
std::swap(marking_start, marking_end);
|
||||
std::swap(msx, mex);
|
||||
}
|
||||
|
||||
bool add_marking_start = msx * int64_t(dir) > int64_t(sx - px) * int64_t(dir) && msx * int64_t(dir) < int64_t(ex - px) * int64_t(dir);
|
||||
bool add_marking_end = mex * int64_t(dir) > int64_t(sx - px) * int64_t(dir) && mex * int64_t(dir) < int64_t(ex - px) * int64_t(dir);
|
||||
|
||||
const Point apex = rot.unapply(Point(0, d / 2)) + pxx;
|
||||
bool add_apex = int64_t(sx - px) * int64_t(dir) < 0 && int64_t(ex - px) * int64_t(dir) > 0;
|
||||
|
||||
assert(!(add_marking_start && add_marking_end) || add_apex);
|
||||
if(add_marking_start && add_marking_end && !add_apex)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(warning) << "Failing to discretize parabola! Must add an apex or one of the endpoints.";
|
||||
}
|
||||
|
||||
const coord_t step_count = static_cast<coord_t>(static_cast<float>(std::abs(ex - sx)) / approximate_step_size + 0.5);
|
||||
|
||||
discretized.emplace_back(s);
|
||||
for (coord_t step = 1; step < step_count; step++)
|
||||
{
|
||||
assert(double(sxex) * double(step) <= double(std::numeric_limits<int64_t>::max()));
|
||||
const int64_t x = int64_t(sx) + int64_t(sxex) * int64_t(step) / int64_t(step_count) - int64_t(px);
|
||||
assert(double(x) * double(x) <= double(std::numeric_limits<int64_t>::max()));
|
||||
assert(double(x) * double(x) / double(2 * d) + double(d / 2) <= double(std::numeric_limits<int64_t>::max()));
|
||||
const int64_t y = int64_t(x) * int64_t(x) / int64_t(2 * d) + int64_t(d / 2);
|
||||
|
||||
if (add_marking_start && msx * int64_t(dir) < int64_t(x) * int64_t(dir))
|
||||
{
|
||||
discretized.emplace_back(marking_start);
|
||||
add_marking_start = false;
|
||||
}
|
||||
if (add_apex && int64_t(x) * int64_t(dir) > 0)
|
||||
{
|
||||
discretized.emplace_back(apex);
|
||||
add_apex = false; // only add the apex just before the
|
||||
}
|
||||
if (add_marking_end && mex * int64_t(dir) < int64_t(x) * int64_t(dir))
|
||||
{
|
||||
discretized.emplace_back(marking_end);
|
||||
add_marking_end = false;
|
||||
}
|
||||
assert(x <= std::numeric_limits<coord_t>::max() && x >= std::numeric_limits<coord_t>::lowest());
|
||||
assert(y <= std::numeric_limits<coord_t>::max() && y >= std::numeric_limits<coord_t>::lowest());
|
||||
const Point result = rot.unapply(Point(x, y)) + pxx;
|
||||
discretized.emplace_back(result);
|
||||
}
|
||||
if (add_apex)
|
||||
{
|
||||
discretized.emplace_back(apex);
|
||||
}
|
||||
if (add_marking_end)
|
||||
{
|
||||
discretized.emplace_back(marking_end);
|
||||
}
|
||||
discretized.emplace_back(e);
|
||||
return discretized;
|
||||
}
|
||||
|
||||
}//namespace Slic3r::Arachne
|
||||
42
src/libslic3r/Arachne/utils/VoronoiUtils.hpp
Normal file
42
src/libslic3r/Arachne/utils/VoronoiUtils.hpp
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
|
||||
#ifndef UTILS_VORONOI_UTILS_H
|
||||
#define UTILS_VORONOI_UTILS_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
|
||||
#include <boost/polygon/voronoi.hpp>
|
||||
|
||||
#include "PolygonsSegmentIndex.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
/*!
|
||||
*/
|
||||
class VoronoiUtils
|
||||
{
|
||||
public:
|
||||
using Segment = PolygonsSegmentIndex;
|
||||
using voronoi_data_t = double;
|
||||
using vd_t = boost::polygon::voronoi_diagram<voronoi_data_t>;
|
||||
|
||||
static Point getSourcePoint(const vd_t::cell_type &cell, const std::vector<Segment> &segments);
|
||||
static const Segment &getSourceSegment(const vd_t::cell_type &cell, const std::vector<Segment> &segments);
|
||||
static PolygonsPointIndex getSourcePointIndex(const vd_t::cell_type &cell, const std::vector<Segment> &segments);
|
||||
|
||||
static Vec2i64 p(const vd_t::vertex_type *node);
|
||||
|
||||
/*!
|
||||
* Discretize a parabola based on (approximate) step size.
|
||||
* The \p approximate_step_size is measured parallel to the \p source_segment, not along the parabola.
|
||||
*/
|
||||
static std::vector<Point> discretizeParabola(const Point &source_point, const Segment &source_segment, Point start, Point end, coord_t approximate_step_size, float transitioning_angle);
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
||||
#endif // UTILS_VORONOI_UTILS_H
|
||||
122
src/libslic3r/Arachne/utils/linearAlg2D.hpp
Normal file
122
src/libslic3r/Arachne/utils/linearAlg2D.hpp
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef UTILS_LINEAR_ALG_2D_H
|
||||
#define UTILS_LINEAR_ALG_2D_H
|
||||
|
||||
#include "../../Point.hpp"
|
||||
|
||||
namespace Slic3r::Arachne::LinearAlg2D
|
||||
{
|
||||
|
||||
/*!
|
||||
* Test whether a point is inside a corner.
|
||||
* Whether point \p query_point is left of the corner abc.
|
||||
* Whether the \p query_point is in the circle half left of ab and left of bc, rather than to the right.
|
||||
*
|
||||
* Test whether the \p query_point is inside of a polygon w.r.t a single corner.
|
||||
*/
|
||||
inline static bool isInsideCorner(const Point &a, const Point &b, const Point &c, const Vec2i64 &query_point)
|
||||
{
|
||||
// Visualisation for the algorithm below:
|
||||
//
|
||||
// query
|
||||
// |
|
||||
// |
|
||||
// |
|
||||
// perp-----------b
|
||||
// / \ (note that the lines
|
||||
// / \ AB and AC are normalized
|
||||
// / \ to 10000 units length)
|
||||
// a c
|
||||
//
|
||||
|
||||
auto normal = [](const Point &p0, coord_t len) -> Point {
|
||||
int64_t _len = p0.cast<int64_t>().norm();
|
||||
if (_len < 1)
|
||||
return {len, 0};
|
||||
return (p0.cast<int64_t>() * int64_t(len) / _len).cast<coord_t>();
|
||||
};
|
||||
|
||||
auto rotate_90_degree_ccw = [](const Vec2d &p) -> Vec2d {
|
||||
return {-p.y(), p.x()};
|
||||
};
|
||||
|
||||
constexpr coord_t normal_length = 10000; //Create a normal vector of reasonable length in order to reduce rounding error.
|
||||
const Point ba = normal(a - b, normal_length);
|
||||
const Point bc = normal(c - b, normal_length);
|
||||
const Vec2d bq = query_point.cast<double>() - b.cast<double>();
|
||||
const Vec2d perpendicular = rotate_90_degree_ccw(bq); //The query projects to this perpendicular to coordinate 0.
|
||||
|
||||
const double project_a_perpendicular = ba.cast<double>().dot(perpendicular); //Project vertex A on the perpendicular line.
|
||||
const double project_c_perpendicular = bc.cast<double>().dot(perpendicular); //Project vertex C on the perpendicular line.
|
||||
if ((project_a_perpendicular > 0.) != (project_c_perpendicular > 0.)) //Query is between A and C on the projection.
|
||||
{
|
||||
return project_a_perpendicular > 0.; //Due to the winding order of corner ABC, this means that the query is inside.
|
||||
}
|
||||
else //Beyond either A or C, but it could still be inside of the polygon.
|
||||
{
|
||||
const double project_a_parallel = ba.cast<double>().dot(bq); //Project not on the perpendicular, but on the original.
|
||||
const double project_c_parallel = bc.cast<double>().dot(bq);
|
||||
|
||||
//Either:
|
||||
// * A is to the right of B (project_a_perpendicular > 0) and C is below A (project_c_parallel < project_a_parallel), or
|
||||
// * A is to the left of B (project_a_perpendicular < 0) and C is above A (project_c_parallel > project_a_parallel).
|
||||
return (project_c_parallel < project_a_parallel) == (project_a_perpendicular > 0.);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Returns the determinant of the 2D matrix defined by the the vectors ab and ap as rows.
|
||||
*
|
||||
* The returned value is zero for \p p lying (approximately) on the line going through \p a and \p b
|
||||
* The value is positive for values lying to the left and negative for values lying to the right when looking from \p a to \p b.
|
||||
*
|
||||
* \param p the point to check
|
||||
* \param a the from point of the line
|
||||
* \param b the to point of the line
|
||||
* \return a positive value when \p p lies to the left of the line from \p a to \p b
|
||||
*/
|
||||
static inline int64_t pointIsLeftOfLine(const Point &p, const Point &a, const Point &b)
|
||||
{
|
||||
return int64_t(b.x() - a.x()) * int64_t(p.y() - a.y()) - int64_t(b.y() - a.y()) * int64_t(p.x() - a.x());
|
||||
}
|
||||
|
||||
/*!
|
||||
* Compute the angle between two consecutive line segments.
|
||||
*
|
||||
* The angle is computed from the left side of b when looking from a.
|
||||
*
|
||||
* c
|
||||
* \ .
|
||||
* \ b
|
||||
* angle|
|
||||
* |
|
||||
* a
|
||||
*
|
||||
* \param a start of first line segment
|
||||
* \param b end of first segment and start of second line segment
|
||||
* \param c end of second line segment
|
||||
* \return the angle in radians between 0 and 2 * pi of the corner in \p b
|
||||
*/
|
||||
static inline float getAngleLeft(const Point &a, const Point &b, const Point &c)
|
||||
{
|
||||
const Vec2i64 ba = (a - b).cast<int64_t>();
|
||||
const Vec2i64 bc = (c - b).cast<int64_t>();
|
||||
const int64_t dott = ba.dot(bc); // dot product
|
||||
const int64_t det = cross2(ba, bc); // determinant
|
||||
if (det == 0) {
|
||||
if ((ba.x() != 0 && (ba.x() > 0) == (bc.x() > 0)) || (ba.x() == 0 && (ba.y() > 0) == (bc.y() > 0)))
|
||||
return 0; // pointy bit
|
||||
else
|
||||
return float(M_PI); // straight bit
|
||||
}
|
||||
const float angle = -atan2(double(det), double(dott)); // from -pi to pi
|
||||
if (angle >= 0)
|
||||
return angle;
|
||||
else
|
||||
return M_PI * 2 + angle;
|
||||
}
|
||||
|
||||
}//namespace Slic3r::Arachne
|
||||
#endif//UTILS_LINEAR_ALG_2D_H
|
||||
|
|
@ -208,7 +208,7 @@ protected:
|
|||
double dist_corner_x = ibb.minCorner().x() - origin_pack.x();
|
||||
if (dist_corner_y < 0 || dist_corner_x<0)
|
||||
return LARGE_COST_TO_REJECT;
|
||||
double bindist = norm(dist_corner_y + 0.1 * dist_corner_x
|
||||
double bindist = norm(dist_corner_y + 1 * dist_corner_x
|
||||
+ 1 * double(ibb.maxCorner().y() - ibb.minCorner().y())); // occupy as few rows as possible
|
||||
return bindist;
|
||||
}
|
||||
|
|
@ -393,7 +393,9 @@ protected:
|
|||
auto py2 = p.boundingBox().maxCorner().y();
|
||||
auto inter_min = std::max(iy1, py1); // min y of intersection
|
||||
auto inter_max = std::min(iy2, py2); // max y of intersection. length=max_y-min_y>0 means intersection exists
|
||||
if (inter_max - inter_min > 0) {
|
||||
// if this item is lower than existing ones, this item will be printed first, so should not exceed height_to_rod
|
||||
if (iy2 < py1) { hasRowHeightConflict |= (item.height > clearance_height_to_rod); }
|
||||
else if (inter_max - inter_min > 0) {
|
||||
// if they inter, the one on the left will be printed first
|
||||
double h = ix1 < px1 ? item.height : p.height;
|
||||
hasRowHeightConflict |= (h > clearance_height_to_rod);
|
||||
|
|
@ -434,10 +436,12 @@ protected:
|
|||
if (!params.allow_multi_materials_on_same_plate)
|
||||
score += LARGE_COST_TO_REJECT * (item.extrude_id != p.extrude_id);
|
||||
}
|
||||
|
||||
// for layered printing, we want extruder change as few as possible
|
||||
// this has very weak effect, CAN NOT use a large weight
|
||||
if (!params.is_seq_print) {
|
||||
extruder_ids.insert(item.extrude_id);
|
||||
score += 0.2 * LARGE_COST_TO_REJECT * std::max(0, ((int)extruder_ids.size() - 1));
|
||||
score += 1 * std::max(0, ((int)extruder_ids.size() - 1));
|
||||
}
|
||||
|
||||
return std::make_tuple(score, fullbb);
|
||||
|
|
|
|||
|
|
@ -69,6 +69,8 @@ set(lisbslic3r_sources
|
|||
Fill/FillConcentric.hpp
|
||||
Fill/FillConcentricWGapFill.cpp
|
||||
Fill/FillConcentricWGapFill.hpp
|
||||
Fill/FillConcentricInternal.cpp
|
||||
Fill/FillConcentricInternal.hpp
|
||||
Fill/FillHoneycomb.cpp
|
||||
Fill/FillHoneycomb.hpp
|
||||
Fill/FillGyroid.cpp
|
||||
|
|
@ -313,6 +315,47 @@ set(lisbslic3r_sources
|
|||
SLA/Clustering.hpp
|
||||
SLA/Clustering.cpp
|
||||
SLA/ReprojectPointsOnMesh.hpp
|
||||
|
||||
Arachne/BeadingStrategy/BeadingStrategy.hpp
|
||||
Arachne/BeadingStrategy/BeadingStrategy.cpp
|
||||
Arachne/BeadingStrategy/BeadingStrategyFactory.hpp
|
||||
Arachne/BeadingStrategy/BeadingStrategyFactory.cpp
|
||||
Arachne/BeadingStrategy/DistributedBeadingStrategy.hpp
|
||||
Arachne/BeadingStrategy/DistributedBeadingStrategy.cpp
|
||||
Arachne/BeadingStrategy/LimitedBeadingStrategy.hpp
|
||||
Arachne/BeadingStrategy/LimitedBeadingStrategy.cpp
|
||||
Arachne/BeadingStrategy/OuterWallInsetBeadingStrategy.hpp
|
||||
Arachne/BeadingStrategy/OuterWallInsetBeadingStrategy.cpp
|
||||
Arachne/BeadingStrategy/RedistributeBeadingStrategy.hpp
|
||||
Arachne/BeadingStrategy/RedistributeBeadingStrategy.cpp
|
||||
Arachne/BeadingStrategy/WideningBeadingStrategy.hpp
|
||||
Arachne/BeadingStrategy/WideningBeadingStrategy.cpp
|
||||
Arachne/utils/ExtrusionJunction.hpp
|
||||
Arachne/utils/ExtrusionJunction.cpp
|
||||
Arachne/utils/ExtrusionLine.hpp
|
||||
Arachne/utils/ExtrusionLine.cpp
|
||||
Arachne/utils/HalfEdge.hpp
|
||||
Arachne/utils/HalfEdgeGraph.hpp
|
||||
Arachne/utils/HalfEdgeNode.hpp
|
||||
Arachne/utils/SparseGrid.hpp
|
||||
Arachne/utils/SparsePointGrid.hpp
|
||||
Arachne/utils/SparseLineGrid.hpp
|
||||
Arachne/utils/SquareGrid.hpp
|
||||
Arachne/utils/SquareGrid.cpp
|
||||
Arachne/utils/PolygonsPointIndex.hpp
|
||||
Arachne/utils/PolygonsSegmentIndex.hpp
|
||||
Arachne/utils/PolylineStitcher.hpp
|
||||
Arachne/utils/PolylineStitcher.cpp
|
||||
Arachne/utils/VoronoiUtils.hpp
|
||||
Arachne/utils/VoronoiUtils.cpp
|
||||
Arachne/SkeletalTrapezoidation.hpp
|
||||
Arachne/SkeletalTrapezoidation.cpp
|
||||
Arachne/SkeletalTrapezoidationEdge.hpp
|
||||
Arachne/SkeletalTrapezoidationGraph.hpp
|
||||
Arachne/SkeletalTrapezoidationGraph.cpp
|
||||
Arachne/SkeletalTrapezoidationJoint.hpp
|
||||
Arachne/WallToolPaths.hpp
|
||||
Arachne/WallToolPaths.cpp
|
||||
)
|
||||
|
||||
if (APPLE)
|
||||
|
|
@ -402,6 +445,11 @@ set(OCCT_LIBS
|
|||
TKernel
|
||||
)
|
||||
|
||||
if(APPLE)
|
||||
target_link_libraries(libslic3r freetype)
|
||||
endif()
|
||||
|
||||
|
||||
target_link_libraries(libslic3r
|
||||
libnest2d
|
||||
admesh
|
||||
|
|
|
|||
|
|
@ -584,6 +584,8 @@ Slic3r::Polygons union_(const Slic3r::Polygons &subject)
|
|||
{ return _clipper(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No); }
|
||||
Slic3r::Polygons union_(const Slic3r::ExPolygons &subject)
|
||||
{ return _clipper(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No); }
|
||||
Slic3r::Polygons union_(const Slic3r::Polygons &subject, const ClipperLib::PolyFillType fillType)
|
||||
{ return to_polygons(clipper_do<ClipperLib::Paths>(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), fillType, ApplySafetyOffset::No)); }
|
||||
Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2)
|
||||
{
|
||||
// BBS
|
||||
|
|
|
|||
|
|
@ -498,6 +498,7 @@ inline Slic3r::Lines intersection_ln(const Slic3r::Line &subject, const Slic3r::
|
|||
|
||||
Slic3r::Polygons union_(const Slic3r::Polygons &subject);
|
||||
Slic3r::Polygons union_(const Slic3r::ExPolygons &subject);
|
||||
Slic3r::Polygons union_(const Slic3r::Polygons &subject, const ClipperLib::PolyFillType fillType);
|
||||
Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2);
|
||||
// May be used to "heal" unusual models (3DLabPrints etc.) by providing fill_type (pftEvenOdd, pftNonZero, pftPositive, pftNegative).
|
||||
Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, ClipperLib::PolyFillType fill_type = ClipperLib::pftNonZero);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include "FillBase.hpp"
|
||||
#include "FillRectilinear.hpp"
|
||||
#include "FillConcentricInternal.hpp"
|
||||
|
||||
#define NARROW_INFILL_AREA_THRESHOLD 3
|
||||
|
||||
|
|
@ -303,7 +304,7 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
|
|||
}
|
||||
}
|
||||
|
||||
// BBS: detect narrow internal solid infill area and use ipConcentricGapFill pattern instead
|
||||
// BBS: detect narrow internal solid infill area and use ipConcentricInternal pattern instead
|
||||
if (layer.object()->config().detect_narrow_internal_solid_infill) {
|
||||
size_t surface_fills_size = surface_fills.size();
|
||||
for (size_t i = 0; i < surface_fills_size; i++) {
|
||||
|
|
@ -324,12 +325,12 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
|
|||
}
|
||||
else if (narrow_expolygons_index.size() == expolygons_size) {
|
||||
// BBS: all expolygons are narrow, directly change the fill pattern
|
||||
surface_fills[i].params.pattern = ipConcentricGapFill;
|
||||
surface_fills[i].params.pattern = ipConcentricInternal;
|
||||
}
|
||||
else {
|
||||
// BBS: some expolygons are narrow, spilit surface_fills[i] and rearrange the expolygons
|
||||
params = surface_fills[i].params;
|
||||
params.pattern = ipConcentricGapFill;
|
||||
params.pattern = ipConcentricInternal;
|
||||
surface_fills.emplace_back(params);
|
||||
surface_fills.back().region_id = surface_fills[i].region_id;
|
||||
surface_fills.back().surface.surface_type = stInternalSolid;
|
||||
|
|
@ -401,6 +402,13 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
|||
f->angle = surface_fill.params.angle;
|
||||
f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree;
|
||||
|
||||
if (surface_fill.params.pattern == ipConcentricInternal) {
|
||||
FillConcentricInternal *fill_concentric = dynamic_cast<FillConcentricInternal *>(f.get());
|
||||
assert(fill_concentric != nullptr);
|
||||
fill_concentric->print_config = &this->object()->print()->config();
|
||||
fill_concentric->print_object_config = &this->object()->config();
|
||||
}
|
||||
|
||||
// calculate flow spacing for infill pattern generation
|
||||
bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.bridge;
|
||||
double link_max_length = 0.;
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
#include "FillLightning.hpp"
|
||||
// BBS: new infill pattern header
|
||||
#include "FillConcentricWGapFill.hpp"
|
||||
#include "FillConcentricInternal.hpp"
|
||||
|
||||
// #define INFILL_DEBUG_OUTPUT
|
||||
|
||||
|
|
@ -58,6 +59,7 @@ Fill* Fill::new_from_type(const InfillPattern type)
|
|||
#endif // HAS_LIGHTNING_INFILL
|
||||
// BBS: for internal solid infill only
|
||||
case ipConcentricGapFill: return new FillConcentricWGapFill();
|
||||
case ipConcentricInternal: return new FillConcentricInternal();
|
||||
// BBS: for bottom and top surface only
|
||||
case ipMonotonicLine: return new FillMonotonicLineWGapFill();
|
||||
default: throw Slic3r::InvalidArgument("unknown type");
|
||||
|
|
|
|||
101
src/libslic3r/Fill/FillConcentricInternal.cpp
Normal file
101
src/libslic3r/Fill/FillConcentricInternal.cpp
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
#include "../ClipperUtils.hpp"
|
||||
#include "../ExPolygon.hpp"
|
||||
#include "../Surface.hpp"
|
||||
#include "../VariableWidth.hpp"
|
||||
#include "Arachne/WallToolPaths.hpp"
|
||||
|
||||
#include "FillConcentricInternal.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void FillConcentricInternal::fill_surface_extrusion(const Surface* surface, const FillParams& params, ExtrusionEntitiesPtr& out)
|
||||
{
|
||||
assert(this->print_config != nullptr && this->print_object_config != nullptr);
|
||||
|
||||
ThickPolylines thick_polylines_out;
|
||||
|
||||
for (size_t i = 0; i < this->no_overlap_expolygons.size(); ++i) {
|
||||
ExPolygon &expolygon = this->no_overlap_expolygons[i];
|
||||
|
||||
// no rotation is supported for this infill pattern
|
||||
Point bbox_size = expolygon.contour.bounding_box().size();
|
||||
coord_t min_spacing = params.flow.scaled_spacing();
|
||||
|
||||
coord_t loops_count = std::max(bbox_size.x(), bbox_size.y()) / min_spacing + 1;
|
||||
Polygons polygons = offset(expolygon, 0);
|
||||
|
||||
double min_nozzle_diameter = *std::min_element(print_config->nozzle_diameter.values.begin(), print_config->nozzle_diameter.values.end());
|
||||
Arachne::WallToolPathsParams input_params;
|
||||
input_params.min_bead_width = 0.85 * min_nozzle_diameter;
|
||||
input_params.min_feature_size = 0.1;
|
||||
input_params.wall_transition_length = 0.4;
|
||||
input_params.wall_transition_angle = 10;
|
||||
input_params.wall_transition_filter_deviation = 0.25 * min_nozzle_diameter;
|
||||
input_params.wall_distribution_count = 1;
|
||||
input_params.wall_add_middle_threshold = 0.75;
|
||||
input_params.wall_split_middle_threshold = 0.5;
|
||||
|
||||
Arachne::WallToolPaths wallToolPaths(polygons, min_spacing, min_spacing, loops_count, 0, input_params);
|
||||
|
||||
std::vector<Arachne::VariableWidthLines> loops = wallToolPaths.getToolPaths();
|
||||
std::vector<const Arachne::ExtrusionLine*> all_extrusions;
|
||||
for (Arachne::VariableWidthLines& loop : loops) {
|
||||
if (loop.empty())
|
||||
continue;
|
||||
for (const Arachne::ExtrusionLine& wall : loop)
|
||||
all_extrusions.emplace_back(&wall);
|
||||
}
|
||||
|
||||
// Split paths using a nearest neighbor search.
|
||||
size_t firts_poly_idx = thick_polylines_out.size();
|
||||
Point last_pos(0, 0);
|
||||
for (const Arachne::ExtrusionLine* extrusion : all_extrusions) {
|
||||
if (extrusion->empty())
|
||||
continue;
|
||||
|
||||
ThickPolyline thick_polyline = Arachne::to_thick_polyline(*extrusion);
|
||||
if (extrusion->is_closed && thick_polyline.points.front() == thick_polyline.points.back() && thick_polyline.width.front() == thick_polyline.width.back()) {
|
||||
thick_polyline.points.pop_back();
|
||||
assert(thick_polyline.points.size() * 2 == thick_polyline.width.size());
|
||||
int nearest_idx = last_pos.nearest_point_index(thick_polyline.points);
|
||||
std::rotate(thick_polyline.points.begin(), thick_polyline.points.begin() + nearest_idx, thick_polyline.points.end());
|
||||
std::rotate(thick_polyline.width.begin(), thick_polyline.width.begin() + 2 * nearest_idx, thick_polyline.width.end());
|
||||
thick_polyline.points.emplace_back(thick_polyline.points.front());
|
||||
}
|
||||
thick_polylines_out.emplace_back(std::move(thick_polyline));
|
||||
}
|
||||
|
||||
// clip the paths to prevent the extruder from getting exactly on the first point of the loop
|
||||
// Keep valid paths only.
|
||||
size_t j = firts_poly_idx;
|
||||
for (size_t i = firts_poly_idx; i < thick_polylines_out.size(); ++i) {
|
||||
thick_polylines_out[i].clip_end(this->loop_clipping);
|
||||
if (thick_polylines_out[i].is_valid()) {
|
||||
if (j < i)
|
||||
thick_polylines_out[j] = std::move(thick_polylines_out[i]);
|
||||
++j;
|
||||
}
|
||||
}
|
||||
if (j < thick_polylines_out.size())
|
||||
thick_polylines_out.erase(thick_polylines_out.begin() + int(j), thick_polylines_out.end());
|
||||
}
|
||||
|
||||
ExtrusionEntityCollection *coll_nosort = new ExtrusionEntityCollection();
|
||||
coll_nosort->no_sort = this->no_sort(); //can be sorted inside the pass
|
||||
|
||||
if (!thick_polylines_out.empty()) {
|
||||
Flow new_flow = params.flow.with_spacing(float(this->spacing));
|
||||
ExtrusionEntityCollection gap_fill;
|
||||
variable_width(thick_polylines_out, params.extrusion_role, new_flow, gap_fill.entities);
|
||||
coll_nosort->append(std::move(gap_fill.entities));
|
||||
}
|
||||
|
||||
if (!coll_nosort->entities.empty())
|
||||
out.push_back(coll_nosort);
|
||||
else
|
||||
delete coll_nosort;
|
||||
|
||||
}
|
||||
|
||||
|
||||
} // namespace Slic3r
|
||||
26
src/libslic3r/Fill/FillConcentricInternal.hpp
Normal file
26
src/libslic3r/Fill/FillConcentricInternal.hpp
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#ifndef slic3r_FillConcentricInternal_hpp_
|
||||
#define slic3r_FillConcentricInternal_hpp_
|
||||
|
||||
#include "FillBase.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class FillConcentricInternal : public Fill
|
||||
{
|
||||
public:
|
||||
~FillConcentricInternal() override = default;
|
||||
void fill_surface_extrusion(const Surface *surface, const FillParams ¶ms, ExtrusionEntitiesPtr &out) override;
|
||||
|
||||
protected:
|
||||
Fill* clone() const override { return new FillConcentricInternal(*this); };
|
||||
bool no_sort() const override { return true; }
|
||||
|
||||
const PrintConfig *print_config = nullptr;
|
||||
const PrintObjectConfig *print_object_config = nullptr;
|
||||
|
||||
friend class Layer;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_FillConcentricInternal_hpp_
|
||||
|
|
@ -553,23 +553,30 @@ bool GCode::gcode_label_objects = false;
|
|||
std::string WipeTowerIntegration::tool_change(GCode& gcodegen, int extruder_id, bool finish_layer)
|
||||
{
|
||||
std::string gcode;
|
||||
|
||||
// Calculate where the wipe tower layer will be printed. -1 means that print z will not change,
|
||||
// resulting in a wipe tower with sparse layers.
|
||||
double wipe_tower_z = -1;
|
||||
bool ignore_sparse = false;
|
||||
if (gcodegen.config().wipe_tower_no_sparse_layers.value) {
|
||||
wipe_tower_z = m_last_wipe_tower_print_z;
|
||||
ignore_sparse = (m_tool_changes[m_layer_idx].size() == 1 && m_tool_changes[m_layer_idx].front().initial_tool == m_tool_changes[m_layer_idx].front().new_tool);
|
||||
if (m_tool_change_idx == 0 && !ignore_sparse)
|
||||
wipe_tower_z = m_last_wipe_tower_print_z + m_tool_changes[m_layer_idx].front().layer_height;
|
||||
}
|
||||
|
||||
if (m_enable_timelapse_print && m_is_first_print) {
|
||||
gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][0], m_tool_changes[m_layer_idx][0].new_tool, wipe_tower_z);
|
||||
m_tool_change_idx++;
|
||||
m_is_first_print = false;
|
||||
}
|
||||
|
||||
assert(m_layer_idx >= 0);
|
||||
if (gcodegen.writer().need_toolchange(extruder_id) || finish_layer) {
|
||||
if (m_layer_idx < (int)m_tool_changes.size()) {
|
||||
if (!(size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size()))
|
||||
throw Slic3r::RuntimeError("Wipe tower generation failed, possibly due to empty first layer.");
|
||||
|
||||
// Calculate where the wipe tower layer will be printed. -1 means that print z will not change,
|
||||
// resulting in a wipe tower with sparse layers.
|
||||
double wipe_tower_z = -1;
|
||||
bool ignore_sparse = false;
|
||||
if (gcodegen.config().wipe_tower_no_sparse_layers.value) {
|
||||
wipe_tower_z = m_last_wipe_tower_print_z;
|
||||
ignore_sparse = (m_tool_changes[m_layer_idx].size() == 1 && m_tool_changes[m_layer_idx].front().initial_tool == m_tool_changes[m_layer_idx].front().new_tool);
|
||||
if (m_tool_change_idx == 0 && !ignore_sparse)
|
||||
wipe_tower_z = m_last_wipe_tower_print_z + m_tool_changes[m_layer_idx].front().layer_height;
|
||||
}
|
||||
|
||||
if (!ignore_sparse) {
|
||||
gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id, wipe_tower_z);
|
||||
m_last_wipe_tower_print_z = wipe_tower_z;
|
||||
|
|
@ -2784,6 +2791,9 @@ GCode::LayerResult GCode::process_layer(
|
|||
}
|
||||
} // for objects
|
||||
|
||||
if (m_wipe_tower)
|
||||
m_wipe_tower->set_is_first_print(true);
|
||||
|
||||
// Extrude the skirt, brim, support, perimeters, infill ordered by the extruders.
|
||||
std::vector<std::unique_ptr<EdgeGrid::Grid>> lower_layer_edge_grids(layers.size());
|
||||
for (unsigned int extruder_id : layer_tools.extruders)
|
||||
|
|
|
|||
|
|
@ -82,7 +82,9 @@ public:
|
|||
m_layer_idx(-1),
|
||||
m_tool_change_idx(0),
|
||||
m_plate_origin(plate_origin),
|
||||
m_single_extruder_multi_material(print_config.single_extruder_multi_material)
|
||||
m_single_extruder_multi_material(print_config.single_extruder_multi_material),
|
||||
m_enable_timelapse_print(print_config.timelapse_no_toolhead.value),
|
||||
m_is_first_print(true)
|
||||
{}
|
||||
|
||||
std::string prime(GCode &gcodegen);
|
||||
|
|
@ -91,6 +93,11 @@ public:
|
|||
std::string finalize(GCode &gcodegen);
|
||||
std::vector<float> used_filament_length() const;
|
||||
|
||||
bool is_first_print() const { return m_is_first_print;}
|
||||
void set_is_first_print(bool is) { m_is_first_print = is; }
|
||||
|
||||
bool enable_timelapse_print() const { return m_enable_timelapse_print; }
|
||||
|
||||
private:
|
||||
WipeTowerIntegration& operator=(const WipeTowerIntegration&);
|
||||
std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const;
|
||||
|
|
@ -117,6 +124,8 @@ private:
|
|||
// BBS
|
||||
Vec3d m_plate_origin;
|
||||
bool m_single_extruder_multi_material;
|
||||
bool m_enable_timelapse_print;
|
||||
bool m_is_first_print;
|
||||
};
|
||||
|
||||
class ColorPrintColors
|
||||
|
|
|
|||
|
|
@ -2998,7 +2998,7 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line)
|
|||
block.role = m_extrusion_role;
|
||||
block.distance = delta_xyz;
|
||||
block.g1_line_id = m_g1_line_id;
|
||||
block.layer_id = m_layer_id;
|
||||
block.layer_id = std::max<unsigned int>(1, m_layer_id);
|
||||
|
||||
// BBS: calculates block cruise feedrate
|
||||
// For arc move, we need to limite the cruise according to centripetal acceleration which is
|
||||
|
|
@ -3685,7 +3685,7 @@ void GCodeProcessor::store_move_vertex(EMoveType type, EMovePathType path_type)
|
|||
m_interpolation_points[i] =
|
||||
Vec3f(m_interpolation_points[i].x() + m_x_offset,
|
||||
m_interpolation_points[i].y() + m_y_offset,
|
||||
m_interpolation_points[i].z()) +
|
||||
m_processing_start_custom_gcode ? m_first_layer_height : m_interpolation_points[i].z()) +
|
||||
m_extruder_offsets[m_extruder_id];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -544,7 +544,8 @@ WipeTower::WipeTower(const PrintConfig& config, int plate_idx, Vec3d plate_origi
|
|||
m_travel_speed(config.travel_speed),
|
||||
m_current_tool(initial_tool),
|
||||
//wipe_volumes(flush_matrix)
|
||||
m_wipe_volume(prime_volume)
|
||||
m_wipe_volume(prime_volume),
|
||||
m_enable_timelapse_print(config.timelapse_no_toolhead.value)
|
||||
{
|
||||
// Read absolute value of first layer speed, if given as percentage,
|
||||
// it is taken over following default. Speeds from config are not
|
||||
|
|
@ -1304,7 +1305,10 @@ void WipeTower::plan_tower()
|
|||
}
|
||||
}
|
||||
|
||||
{
|
||||
{
|
||||
if (m_enable_timelapse_print && max_depth < EPSILON)
|
||||
max_depth = min_wipe_tower_depth;
|
||||
|
||||
if (max_depth + EPSILON < min_wipe_tower_depth)
|
||||
m_extra_spacing = min_wipe_tower_depth / max_depth;
|
||||
else
|
||||
|
|
@ -1343,9 +1347,13 @@ void WipeTower::plan_tower()
|
|||
for (auto& layer : m_plan)
|
||||
layer.depth = 0.f;
|
||||
|
||||
float max_depth_for_all = 0;
|
||||
for (int layer_index = int(m_plan.size()) - 1; layer_index >= 0; --layer_index)
|
||||
{
|
||||
float this_layer_depth = std::max(m_plan[layer_index].depth, m_plan[layer_index].toolchanges_depth());
|
||||
if (m_enable_timelapse_print && this_layer_depth < EPSILON)
|
||||
this_layer_depth = min_wipe_tower_depth;
|
||||
|
||||
m_plan[layer_index].depth = this_layer_depth;
|
||||
|
||||
if (this_layer_depth > m_wipe_tower_depth - m_perimeter_width)
|
||||
|
|
@ -1356,7 +1364,16 @@ void WipeTower::plan_tower()
|
|||
if (m_plan[i].depth - this_layer_depth < 2*m_perimeter_width )
|
||||
m_plan[i].depth = this_layer_depth;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_enable_timelapse_print && layer_index == 0)
|
||||
max_depth_for_all = m_plan[0].depth;
|
||||
}
|
||||
|
||||
if (m_enable_timelapse_print) {
|
||||
for (int i = int(m_plan.size()) - 1; i >= 0; i--) {
|
||||
m_plan[i].depth = max_depth_for_all;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WipeTower::save_on_last_wipe()
|
||||
|
|
@ -1474,17 +1491,25 @@ void WipeTower::generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &
|
|||
// BBS: consider both soluable and support properties
|
||||
int idx = first_toolchange_to_nonsoluble_nonsupport (layer.tool_changes);
|
||||
ToolChangeResult finish_layer_tcr;
|
||||
ToolChangeResult timelapse_wall;
|
||||
|
||||
if (idx == -1) {
|
||||
// if there is no toolchange switching to non-soluble, finish layer
|
||||
// will be called at the very beginning. That's the last possibility
|
||||
// where a nonsoluble tool can be.
|
||||
finish_layer_tcr = finish_layer();
|
||||
if (m_enable_timelapse_print) {
|
||||
timelapse_wall = only_generate_out_wall();
|
||||
}
|
||||
finish_layer_tcr = finish_layer(m_enable_timelapse_print ? false : true);
|
||||
}
|
||||
|
||||
for (int i=0; i<int(layer.tool_changes.size()); ++i) {
|
||||
if (i == 0 && m_enable_timelapse_print) {
|
||||
timelapse_wall = only_generate_out_wall();
|
||||
}
|
||||
|
||||
if (i == idx) {
|
||||
layer_result.emplace_back(tool_change(layer.tool_changes[i].new_tool, true));
|
||||
layer_result.emplace_back(tool_change(layer.tool_changes[i].new_tool, m_enable_timelapse_print ? false : true));
|
||||
// finish_layer will be called after this toolchange
|
||||
finish_layer_tcr = finish_layer(false);
|
||||
}
|
||||
|
|
@ -1504,8 +1529,57 @@ void WipeTower::generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &
|
|||
layer_result[idx] = merge_tcr(layer_result[idx], finish_layer_tcr);
|
||||
}
|
||||
|
||||
if (m_enable_timelapse_print) {
|
||||
layer_result.insert(layer_result.begin(), std::move(timelapse_wall));
|
||||
}
|
||||
|
||||
result.emplace_back(std::move(layer_result));
|
||||
}
|
||||
}
|
||||
|
||||
WipeTower::ToolChangeResult WipeTower::only_generate_out_wall()
|
||||
{
|
||||
size_t old_tool = m_current_tool;
|
||||
|
||||
m_extrusion_flow = 0.038f; // hard code
|
||||
WipeTowerWriter writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar);
|
||||
writer.set_extrusion_flow(m_extrusion_flow)
|
||||
.set_z(m_z_pos)
|
||||
.set_initial_tool(m_current_tool)
|
||||
.set_y_shift(m_y_shift - (m_current_shape == SHAPE_REVERSED ? m_layer_info->toolchanges_depth() : 0.f));
|
||||
|
||||
// Slow down on the 1st layer.
|
||||
bool first_layer = is_first_layer();
|
||||
// BBS: speed up perimeter speed to 90mm/s for non-first layer
|
||||
float feedrate = first_layer ? std::min(m_first_layer_speed * 60.f, 5400.f) : 5400.f;
|
||||
float fill_box_y = m_layer_info->toolchanges_depth() + m_perimeter_width;
|
||||
box_coordinates fill_box(Vec2f(m_perimeter_width, fill_box_y), m_wipe_tower_width - 2 * m_perimeter_width, m_layer_info->depth - fill_box_y);
|
||||
|
||||
writer.set_initial_position((m_left_to_right ? fill_box.ru : fill_box.lu), // so there is never a diagonal travel
|
||||
m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation);
|
||||
|
||||
bool toolchanges_on_layer = m_layer_info->toolchanges_depth() > WT_EPSILON;
|
||||
|
||||
// we are in one of the corners, travel to ld along the perimeter:
|
||||
if (writer.x() > fill_box.ld.x() + EPSILON) writer.travel(fill_box.ld.x(), writer.y());
|
||||
if (writer.y() > fill_box.ld.y() + EPSILON) writer.travel(writer.x(), fill_box.ld.y());
|
||||
|
||||
// outer perimeter (always):
|
||||
// BBS
|
||||
box_coordinates wt_box(Vec2f(0.f, (m_current_shape == SHAPE_REVERSED ? m_layer_info->toolchanges_depth() : 0.f)), m_wipe_tower_width, m_layer_info->depth + m_perimeter_width);
|
||||
wt_box = align_perimeter(wt_box);
|
||||
writer.rectangle(wt_box, feedrate);
|
||||
|
||||
// Now prepare future wipe. box contains rectangle that was extruded last (ccw).
|
||||
Vec2f target = (writer.pos() == wt_box.ld ? wt_box.rd : (writer.pos() == wt_box.rd ? wt_box.ru : (writer.pos() == wt_box.ru ? wt_box.lu : wt_box.ld)));
|
||||
writer.add_wipe_point(writer.pos()).add_wipe_point(target);
|
||||
|
||||
// Ask our writer about how much material was consumed.
|
||||
// Skip this in case the layer is sparse and config option to not print sparse layers is enabled.
|
||||
if (!m_no_sparse_layers || toolchanges_on_layer)
|
||||
if (m_current_tool < m_used_filament_length.size()) m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
|
||||
|
||||
return construct_tcr(writer, false, old_tool, true, 0.f);
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
|
|
|||
|
|
@ -152,6 +152,8 @@ public:
|
|||
// Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result"
|
||||
void generate(std::vector<std::vector<ToolChangeResult>> &result);
|
||||
|
||||
WipeTower::ToolChangeResult only_generate_out_wall();
|
||||
|
||||
float get_depth() const { return m_wipe_tower_depth; }
|
||||
float get_brim_width() const { return m_wipe_tower_brim_width_real; }
|
||||
|
||||
|
|
@ -263,7 +265,7 @@ private:
|
|||
return m_filpar[0].filament_area; // all extruders are assumed to have the same filament diameter at this point
|
||||
}
|
||||
|
||||
|
||||
bool m_enable_timelapse_print = false;
|
||||
bool m_semm = true; // Are we using a single extruder multimaterial printer?
|
||||
Vec2f m_wipe_tower_pos; // Left front corner of the wipe tower in mm.
|
||||
float m_wipe_tower_width; // Width of the wipe tower.
|
||||
|
|
|
|||
|
|
@ -82,6 +82,44 @@ double distance_to(const L &line, const Vec<Dim<L>, Scalar<L>> &point)
|
|||
return std::sqrt(distance_to_squared(line, point));
|
||||
}
|
||||
|
||||
// Returns a squared distance to the closest point on the infinite.
|
||||
// Returned nearest_point (and returned squared distance to this point) could be beyond the 'a' and 'b' ends of the segment.
|
||||
template<class L>
|
||||
double distance_to_infinite_squared(const L &line, const Vec<Dim<L>, Scalar<L>> &point, Vec<Dim<L>, Scalar<L>> *closest_point)
|
||||
{
|
||||
const Vec<Dim<L>, double> v = (get_b(line) - get_a(line)).template cast<double>();
|
||||
const Vec<Dim<L>, double> va = (point - get_a(line)).template cast<double>();
|
||||
const double l2 = v.squaredNorm(); // avoid a sqrt
|
||||
if (l2 == 0.) {
|
||||
// a == b case
|
||||
*closest_point = get_a(line);
|
||||
return va.squaredNorm();
|
||||
}
|
||||
// Consider the line extending the segment, parameterized as a + t (b - a).
|
||||
// We find projection of this point onto the line.
|
||||
// It falls where t = [(this-a) . (b-a)] / |b-a|^2
|
||||
const double t = va.dot(v) / l2;
|
||||
*closest_point = (get_a(line).template cast<double>() + t * v).template cast<Scalar<L>>();
|
||||
return (t * v - va).squaredNorm();
|
||||
}
|
||||
|
||||
// Returns a squared distance to the closest point on the infinite.
|
||||
// Closest point (and returned squared distance to this point) could be beyond the 'a' and 'b' ends of the segment.
|
||||
template<class L>
|
||||
double distance_to_infinite_squared(const L &line, const Vec<Dim<L>, Scalar<L>> &point)
|
||||
{
|
||||
Vec<Dim<L>, Scalar<L>> nearest_point;
|
||||
return distance_to_infinite_squared<L>(line, point, &nearest_point);
|
||||
}
|
||||
|
||||
// Returns a distance to the closest point on the infinite.
|
||||
// Closest point (and returned squared distance to this point) could be beyond the 'a' and 'b' ends of the segment.
|
||||
template<class L>
|
||||
double distance_to_infinite(const L &line, const Vec<Dim<L>, Scalar<L>> &point)
|
||||
{
|
||||
return std::sqrt(distance_to_infinite_squared(line, point));
|
||||
}
|
||||
|
||||
} // namespace line_alg
|
||||
|
||||
class Line
|
||||
|
|
@ -102,6 +140,7 @@ public:
|
|||
double distance_to_squared(const Point &point) const { return distance_to_squared(point, this->a, this->b); }
|
||||
double distance_to_squared(const Point &point, Point *closest_point) const { return line_alg::distance_to_squared(*this, point, closest_point); }
|
||||
double distance_to(const Point &point) const { return distance_to(point, this->a, this->b); }
|
||||
double distance_to_infinite_squared(const Point &point, Point *closest_point) const { return line_alg::distance_to_infinite_squared(*this, point, closest_point); }
|
||||
double perp_distance_to(const Point &point) const;
|
||||
bool parallel_to(double angle) const;
|
||||
bool parallel_to(const Line& line) const;
|
||||
|
|
@ -122,6 +161,11 @@ public:
|
|||
static inline double distance_to_squared(const Point &point, const Point &a, const Point &b) { return line_alg::distance_to_squared(Line{a, b}, Vec<2, coord_t>{point}); }
|
||||
static double distance_to(const Point &point, const Point &a, const Point &b) { return sqrt(distance_to_squared(point, a, b)); }
|
||||
|
||||
// Returns a distance to the closest point on the infinite.
|
||||
// Closest point (and returned squared distance to this point) could be beyond the 'a' and 'b' ends of the segment.
|
||||
static inline double distance_to_infinite_squared(const Point &point, const Point &a, const Point &b) { return line_alg::distance_to_infinite_squared(Line{a, b}, Vec<2, coord_t>{point}); }
|
||||
static double distance_to_infinite(const Point &point, const Point &a, const Point &b) { return sqrt(distance_to_infinite_squared(point, a, b)); }
|
||||
|
||||
Point a;
|
||||
Point b;
|
||||
|
||||
|
|
|
|||
|
|
@ -237,8 +237,7 @@ enum class ModelVolumeType : int {
|
|||
NEGATIVE_VOLUME,
|
||||
PARAMETER_MODIFIER,
|
||||
SUPPORT_BLOCKER,
|
||||
SUPPORT_ENFORCER,
|
||||
TIMELAPSE_WIPE_TOWER
|
||||
SUPPORT_ENFORCER
|
||||
};
|
||||
|
||||
enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, FlipLower, CutToParts };
|
||||
|
|
@ -271,7 +270,6 @@ public:
|
|||
LayerHeightProfile layer_height_profile;
|
||||
// Whether or not this object is printable
|
||||
bool printable;
|
||||
bool is_timelapse_wipe_tower = false;
|
||||
|
||||
// This vector holds position of selected support points for SLA. The data are
|
||||
// saved in mesh coordinates to allow using them for several instances.
|
||||
|
|
|
|||
|
|
@ -138,11 +138,9 @@ ArrangePolygon get_instance_arrange_poly(ModelInstance* instance, const Slic3r::
|
|||
ap.print_temp = config.opt_int("nozzle_temperature", ap.extrude_ids.back() - 1);
|
||||
if (config.has("nozzle_temperature_initial_layer")) //get the nozzle_temperature_initial_layer
|
||||
ap.first_print_temp = config.opt_int("nozzle_temperature_initial_layer", ap.extrude_ids.back() - 1);
|
||||
// BBS: since first_bed_temp packs all 3 temperatures, vitrify_temp should follow same routine
|
||||
|
||||
if (config.has("temperature_vitrification")) {
|
||||
int tmp = config.opt_int("temperature_vitrification", ap.extrude_ids.back() - 1);
|
||||
for (int i = 0; i < BedType::btCount; i++)
|
||||
ap.vitrify_temp += tmp * pow(100, BedType::btCount - i - 1);
|
||||
ap.vitrify_temp = config.opt_int("temperature_vitrification", ap.extrude_ids.back() - 1);
|
||||
}
|
||||
|
||||
// get brim width
|
||||
|
|
|
|||
|
|
@ -396,9 +396,8 @@ void PerimeterGenerator::process()
|
|||
int loop_number = this->config->wall_loops + surface.extra_perimeters - 1; // 0-indexed loops
|
||||
if (this->layer_id == 0 && this->config->only_one_wall_first_layer)
|
||||
loop_number = 0;
|
||||
|
||||
// BBS: force the topmost layer to be one wall
|
||||
if (loop_number > 0 && this->upper_slices == nullptr)
|
||||
//BBS: set the topmost layer to be one wall
|
||||
if (loop_number > 0 && config->only_one_wall_top && this->upper_slices == nullptr)
|
||||
loop_number = 0;
|
||||
|
||||
ExPolygons last = union_ex(surface.expolygon.simplify_p(surface_simplify_resolution));
|
||||
|
|
|
|||
|
|
@ -163,6 +163,7 @@ public:
|
|||
Point rotated(double angle) const { Point res(*this); res.rotate(angle); return res; }
|
||||
Point rotated(double cos_a, double sin_a) const { Point res(*this); res.rotate(cos_a, sin_a); return res; }
|
||||
Point rotated(double angle, const Point ¢er) const { Point res(*this); res.rotate(angle, center); return res; }
|
||||
Point rotate_90_degree_ccw() const { return Point(-this->y(), this->x()); }
|
||||
int nearest_point_index(const Points &points) const;
|
||||
int nearest_point_index(const PointConstPtrs &points) const;
|
||||
int nearest_point_index(const PointPtrs &points) const;
|
||||
|
|
@ -245,6 +246,15 @@ inline bool has_duplicate_successive_points_closed(const std::vector<Point> &pts
|
|||
return has_duplicate_successive_points(pts) || (pts.size() >= 2 && pts.front() == pts.back());
|
||||
}
|
||||
|
||||
inline bool shorter_then(const Point& p0, const coord_t len)
|
||||
{
|
||||
if (p0.x() > len || p0.x() < -len)
|
||||
return false;
|
||||
if (p0.y() > len || p0.y() < -len)
|
||||
return false;
|
||||
return p0.cast<int64_t>().squaredNorm() <= Slic3r::sqr(int64_t(len));
|
||||
}
|
||||
|
||||
namespace int128 {
|
||||
// Exact orientation predicate,
|
||||
// returns +1: CCW, 0: collinear, -1: CW.
|
||||
|
|
|
|||
|
|
@ -1577,6 +1577,9 @@ void Print::finalize_first_layer_convex_hull()
|
|||
// Wipe tower support.
|
||||
bool Print::has_wipe_tower() const
|
||||
{
|
||||
if (enable_timelapse_print())
|
||||
return true;
|
||||
|
||||
return
|
||||
! m_config.spiral_mode.value &&
|
||||
m_config.enable_prime_tower.value &&
|
||||
|
|
@ -1591,18 +1594,25 @@ const WipeTowerData& Print::wipe_tower_data(size_t filaments_cnt) const
|
|||
double width = m_config.prime_tower_width;
|
||||
double layer_height = 0.2; // hard code layer height
|
||||
double wipe_volume = m_config.prime_volume;
|
||||
const_cast<Print*>(this)->m_wipe_tower_data.depth = wipe_volume * (filaments_cnt - 1) / (layer_height * width);
|
||||
if (filaments_cnt == 1 && enable_timelapse_print()) {
|
||||
const_cast<Print *>(this)->m_wipe_tower_data.depth = wipe_volume / (layer_height * width);
|
||||
} else {
|
||||
const_cast<Print *>(this)->m_wipe_tower_data.depth = wipe_volume * (filaments_cnt - 1) / (layer_height * width);
|
||||
}
|
||||
const_cast<Print*>(this)->m_wipe_tower_data.brim_width = m_config.prime_tower_brim_width;
|
||||
}
|
||||
|
||||
return m_wipe_tower_data;
|
||||
}
|
||||
|
||||
bool Print::enable_timelapse_print() const
|
||||
{
|
||||
return m_config.timelapse_no_toolhead.value;
|
||||
}
|
||||
|
||||
void Print::_make_wipe_tower()
|
||||
{
|
||||
m_wipe_tower_data.clear();
|
||||
if (! this->has_wipe_tower())
|
||||
return;
|
||||
|
||||
// Get wiping matrix to get number of extruders and convert vector<double> to vector<float>:
|
||||
std::vector<float> flush_matrix(cast<float>(m_config.flush_volumes_matrix.values));
|
||||
|
|
@ -1618,7 +1628,18 @@ void Print::_make_wipe_tower()
|
|||
// BBS: priming logic is removed, so don't consider it in tool ordering
|
||||
m_wipe_tower_data.tool_ordering = ToolOrdering(*this, (unsigned int)-1, false);
|
||||
|
||||
if (! m_wipe_tower_data.tool_ordering.has_wipe_tower())
|
||||
// if enable_timelapse_print(), update all layer_tools parameters(has_wipe_tower, wipe_tower_partitions)
|
||||
if (enable_timelapse_print()) {
|
||||
std::vector<LayerTools>& layer_tools_array = m_wipe_tower_data.tool_ordering.layer_tools();
|
||||
for (LayerTools& layer_tools : layer_tools_array) {
|
||||
layer_tools.has_wipe_tower = true;
|
||||
if (layer_tools.wipe_tower_partitions == 0) {
|
||||
layer_tools.wipe_tower_partitions = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_wipe_tower_data.tool_ordering.has_wipe_tower())
|
||||
// Don't generate any wipe tower.
|
||||
return;
|
||||
|
||||
|
|
@ -1707,6 +1728,11 @@ void Print::_make_wipe_tower()
|
|||
}
|
||||
}
|
||||
layer_tools.wiping_extrusions().ensure_perimeters_infills_order(*this);
|
||||
|
||||
// if enable timelapse, slice all layer
|
||||
if (enable_timelapse_print())
|
||||
continue;
|
||||
|
||||
if (&layer_tools == &m_wipe_tower_data.tool_ordering.back() || (&layer_tools + 1)->wipe_tower_partitions == 0)
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -416,7 +416,7 @@ private:
|
|||
friend class Print;
|
||||
|
||||
PrintObject(Print* print, ModelObject* model_object, const Transform3d& trafo, PrintInstances&& instances);
|
||||
~PrintObject() { if (m_shared_regions && -- m_shared_regions->m_ref_cnt == 0) delete m_shared_regions; }
|
||||
~PrintObject();
|
||||
|
||||
void config_apply(const ConfigBase &other, bool ignore_nonexistent = false) { m_config.apply(other, ignore_nonexistent); }
|
||||
void config_apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false) { m_config.apply_only(other, keys, ignore_nonexistent); }
|
||||
|
|
@ -684,6 +684,8 @@ public:
|
|||
const WipeTowerData& wipe_tower_data(size_t filaments_cnt = 0) const;
|
||||
const ToolOrdering& tool_ordering() const { return m_tool_ordering; }
|
||||
|
||||
bool enable_timelapse_print() const;
|
||||
|
||||
std::string output_filename(const std::string &filename_base = std::string()) const override;
|
||||
|
||||
size_t num_print_regions() const throw() { return m_print_regions.size(); }
|
||||
|
|
|
|||
|
|
@ -577,7 +577,7 @@ void PrintConfigDef::init_fff_params()
|
|||
def->tooltip = L("Decrease this value slightly(for example 0.9) to reduce the amount of material for bridge, "
|
||||
"to improve sag");
|
||||
def->min = 0;
|
||||
def->max = 1;
|
||||
def->max = 2.0;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(1));
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ enum AuthorizationType {
|
|||
|
||||
enum InfillPattern : int {
|
||||
ipConcentric, ipRectilinear, ipGrid, ipLine, ipCubic, ipTriangles, ipStars, ipGyroid, ipHoneycomb, ipAdaptiveCubic, ipMonotonic, ipMonotonicLine, ipAlignedRectilinear, ip3DHoneycomb,
|
||||
ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipSupportCubic, ipSupportBase, ipConcentricGapFill,
|
||||
ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipSupportCubic, ipSupportBase, ipConcentricGapFill, ipConcentricInternal,
|
||||
#if HAS_LIGHTNING_INFILL
|
||||
ipLightning,
|
||||
#endif // HAS_LIGHTNING_INFILL
|
||||
|
|
|
|||
|
|
@ -84,6 +84,14 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, const Transfor
|
|||
this->set_instances(std::move(instances));
|
||||
}
|
||||
|
||||
PrintObject::~PrintObject()
|
||||
{
|
||||
if (m_shared_regions && -- m_shared_regions->m_ref_cnt == 0) delete m_shared_regions;
|
||||
clear_layers();
|
||||
clear_support_layers();
|
||||
clear_tree_support_layers();
|
||||
}
|
||||
|
||||
PrintBase::ApplyStatus PrintObject::set_instances(PrintInstances &&instances)
|
||||
{
|
||||
for (PrintInstance &i : instances)
|
||||
|
|
@ -659,8 +667,7 @@ bool PrintObject::invalidate_state_by_config_options(
|
|||
//BBS
|
||||
|| opt_key == "adaptive_layer_height"
|
||||
|| opt_key == "raft_layers"
|
||||
|| opt_key == "raft_contact_distance"
|
||||
|| opt_key == "timelapse_no_toolhead") {
|
||||
|| opt_key == "raft_contact_distance") {
|
||||
steps.emplace_back(posSlice);
|
||||
} else if (
|
||||
opt_key == "elefant_foot_compensation"
|
||||
|
|
@ -2376,9 +2383,9 @@ void PrintObject::remove_bridges_from_contacts(
|
|||
int x0 = bbox.min.x();
|
||||
int x1 = bbox.max.x();
|
||||
int y0 = bbox.min.y();
|
||||
int y1 = bbox.max.y();
|
||||
int y1 = bbox.max.y();
|
||||
const int grid_lw = int(w/2); // grid line width
|
||||
|
||||
|
||||
#if 1
|
||||
if (fabs(surface.bridge_angle-0)<fabs(surface.bridge_angle-M_PI_2)) {
|
||||
int step = bbox_size(0) / ceil(bbox_size(0) / max_bridge_length);
|
||||
|
|
|
|||
|
|
@ -2,21 +2,22 @@
|
|||
|
||||
namespace Slic3r {
|
||||
|
||||
static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline& thick_polyline, ExtrusionRole role, const Flow& flow, const float tolerance)
|
||||
ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thick_polyline, ExtrusionRole role, const Flow &flow, const float tolerance, const float merge_tolerance)
|
||||
{
|
||||
ExtrusionPaths paths;
|
||||
ExtrusionPath path(role);
|
||||
ThickLines lines = thick_polyline.thicklines();
|
||||
|
||||
|
||||
for (int i = 0; i < (int)lines.size(); ++i) {
|
||||
const ThickLine& line = lines[i];
|
||||
assert(line.a_width >= SCALED_EPSILON && line.b_width >= SCALED_EPSILON);
|
||||
|
||||
const coordf_t line_len = line.length();
|
||||
if (line_len < SCALED_EPSILON) continue;
|
||||
|
||||
double thickness_delta = fabs(line.a_width - line.b_width);
|
||||
if (thickness_delta > tolerance) {
|
||||
const unsigned int segments = (unsigned int)ceil(thickness_delta / tolerance);
|
||||
const auto segments = (unsigned int)ceil(thickness_delta / tolerance);
|
||||
const coordf_t seg_len = line_len / segments;
|
||||
Points pp;
|
||||
std::vector<coordf_t> width;
|
||||
|
|
@ -26,7 +27,7 @@ static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline& thi
|
|||
for (size_t j = 1; j < segments; ++j) {
|
||||
pp.push_back((line.a.cast<double>() + (line.b - line.a).cast<double>().normalized() * (j * seg_len)).cast<coord_t>());
|
||||
|
||||
coordf_t w = line.a_width + (j * seg_len) * (line.b_width - line.a_width) / line_len;
|
||||
coordf_t w = line.a_width + (j*seg_len) * (line.b_width-line.a_width) / line_len;
|
||||
width.push_back(w);
|
||||
width.push_back(w);
|
||||
}
|
||||
|
|
@ -34,48 +35,47 @@ static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline& thi
|
|||
width.push_back(line.b_width);
|
||||
|
||||
assert(pp.size() == segments + 1u);
|
||||
assert(width.size() == segments * 2);
|
||||
assert(width.size() == segments*2);
|
||||
}
|
||||
|
||||
// delete this line and insert new ones
|
||||
lines.erase(lines.begin() + i);
|
||||
for (size_t j = 0; j < segments; ++j) {
|
||||
ThickLine new_line(pp[j], pp[j + 1]);
|
||||
new_line.a_width = width[2 * j];
|
||||
new_line.b_width = width[2 * j + 1];
|
||||
ThickLine new_line(pp[j], pp[j+1]);
|
||||
new_line.a_width = width[2*j];
|
||||
new_line.b_width = width[2*j+1];
|
||||
lines.insert(lines.begin() + i + j, new_line);
|
||||
}
|
||||
|
||||
--i;
|
||||
-- i;
|
||||
continue;
|
||||
}
|
||||
|
||||
const double w = fmax(line.a_width, line.b_width);
|
||||
const double w = fmax(line.a_width, line.b_width);
|
||||
const Flow new_flow = (role == erOverhangPerimeter && flow.bridge()) ? flow : flow.with_width(unscale<float>(w) + flow.height() * float(1. - 0.25 * PI));
|
||||
if (path.polyline.points.empty()) {
|
||||
path.polyline.append(line.a);
|
||||
path.polyline.append(line.b);
|
||||
// Convert from spacing to extrusion width based on the extrusion model
|
||||
// of a square extrusion ended with semi circles.
|
||||
Flow new_flow = flow.with_width(unscale<float>(w) + flow.height() * float(1. - 0.25 * PI));
|
||||
#ifdef SLIC3R_DEBUG
|
||||
#ifdef SLIC3R_DEBUG
|
||||
printf(" filling %f gap\n", flow.width);
|
||||
#endif
|
||||
path.mm3_per_mm = new_flow.mm3_per_mm();
|
||||
path.width = new_flow.width();
|
||||
path.height = new_flow.height();
|
||||
}
|
||||
else {
|
||||
thickness_delta = fabs(scale_(flow.width()) - w);
|
||||
if (thickness_delta <= tolerance) {
|
||||
// the width difference between this line and the current flow width is
|
||||
// within the accepted tolerance
|
||||
#endif
|
||||
path.mm3_per_mm = new_flow.mm3_per_mm();
|
||||
path.width = new_flow.width();
|
||||
path.height = new_flow.height();
|
||||
} else {
|
||||
assert(path.width >= EPSILON);
|
||||
thickness_delta = scaled<double>(fabs(path.width - new_flow.width()));
|
||||
if (thickness_delta <= merge_tolerance) {
|
||||
// the width difference between this line and the current flow
|
||||
// (of the previous line) width is within the accepted tolerance
|
||||
path.polyline.append(line.b);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// we need to initialize a new line
|
||||
paths.emplace_back(std::move(path));
|
||||
path = ExtrusionPath(role);
|
||||
--i;
|
||||
-- i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#include "Flow.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline& thick_polyline, ExtrusionRole role, const Flow& flow, const float tolerance, const float merge_tolerance);
|
||||
void variable_width(const ThickPolylines& polylines, ExtrusionRole role, const Flow& flow, std::vector<ExtrusionEntity*>& out);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -72,6 +72,8 @@ set(SLIC3R_GUI_SOURCES
|
|||
GUI/Widgets/wxStaticText2.hpp
|
||||
GUI/AboutDialog.cpp
|
||||
GUI/AboutDialog.hpp
|
||||
GUI/NetworkTestDialog.cpp
|
||||
GUI/NetworkTestDialog.hpp
|
||||
GUI/AuxiliaryDialog.cpp
|
||||
GUI/AuxiliaryDialog.hpp
|
||||
GUI/Auxiliary.cpp
|
||||
|
|
@ -217,8 +219,8 @@ set(SLIC3R_GUI_SOURCES
|
|||
GUI/MonitorPage.hpp
|
||||
GUI/StatusPanel.cpp
|
||||
GUI/StatusPanel.hpp
|
||||
GUI/UpdateErrorMessage.cpp
|
||||
GUI/UpdateErrorMessage.hpp
|
||||
GUI/HMS.hpp
|
||||
GUI/HMS.cpp
|
||||
GUI/SliceInfoPanel.cpp
|
||||
GUI/SliceInfoPanel.hpp
|
||||
GUI/CameraPopup.cpp
|
||||
|
|
@ -426,8 +428,8 @@ source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SLIC3R_GUI_SOURCES})
|
|||
|
||||
encoding_check(libslic3r_gui)
|
||||
|
||||
target_link_libraries(libslic3r_gui libslic3r cereal imgui minilzo GLEW::GLEW OpenGL::GL hidapi libcurl OpenSSL::SSL OpenSSL::Crypto
|
||||
${wxWidgets_LIBRARIES} glfw)
|
||||
target_link_libraries(libslic3r_gui libslic3r cereal imgui minilzo GLEW::GLEW OpenGL::GL hidapi ${wxWidgets_LIBRARIES} glfw libcurl OpenSSL::SSL OpenSSL::Crypto)
|
||||
#target_link_libraries(libslic3r_gui libslic3r cereal imgui minilzo GLEW::GLEW OpenGL::GL hidapi libcurl OpenSSL::SSL OpenSSL::Crypto ${wxWidgets_LIBRARIES} glfw)
|
||||
|
||||
if (MSVC)
|
||||
target_link_libraries(libslic3r_gui Setupapi.lib)
|
||||
|
|
|
|||
|
|
@ -83,6 +83,21 @@ std::vector<std::array<float, 4>> get_extruders_colors()
|
|||
return colors_out;
|
||||
}
|
||||
|
||||
std::array<float, 4> adjust_color_for_rendering(const std::array<float, 4>& colors)
|
||||
{
|
||||
if ((colors[0] < 0.1) && (colors[1] < 0.1) && (colors[2] < 0.1))
|
||||
{
|
||||
std::array<float, 4> new_color;
|
||||
new_color[0] = 0.1;
|
||||
new_color[1] = 0.1;
|
||||
new_color[2] = 0.1;
|
||||
new_color[3] = colors[3];
|
||||
return new_color;
|
||||
}
|
||||
|
||||
return colors;
|
||||
}
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
#if ENABLE_SMOOTH_NORMALS
|
||||
|
|
@ -488,8 +503,11 @@ void GLVolume::set_render_color()
|
|||
else if (is_outside && shader_outside_printer_detection_enabled)
|
||||
set_render_color(OUTSIDE_COLOR);
|
||||
#endif
|
||||
else
|
||||
set_render_color(color);
|
||||
else {
|
||||
//to make black not too hard too see
|
||||
std::array<float, 4> new_color = adjust_color_for_rendering(color);
|
||||
set_render_color(new_color);
|
||||
}
|
||||
}
|
||||
|
||||
if (force_transparent)
|
||||
|
|
@ -708,13 +726,24 @@ void GLVolume::render(bool with_outline) const
|
|||
ModelObject* mo = model_objects[object_idx()];
|
||||
ModelVolume* mv = mo->volumes[volume_idx()];
|
||||
int extruder_id = mv->extruder_id();
|
||||
shader->set_uniform("uniform_color", colors[extruder_id - 1]);
|
||||
//shader->set_uniform("uniform_color", colors[extruder_id - 1]);
|
||||
//to make black not too hard too see
|
||||
std::array<float, 4> new_color = adjust_color_for_rendering(colors[extruder_id - 1]);
|
||||
shader->set_uniform("uniform_color", new_color);
|
||||
}
|
||||
else {
|
||||
if (idx <= colors.size())
|
||||
shader->set_uniform("uniform_color", colors[idx - 1]);
|
||||
else
|
||||
shader->set_uniform("uniform_color", colors[0]);
|
||||
if (idx <= colors.size()) {
|
||||
//shader->set_uniform("uniform_color", colors[idx - 1]);
|
||||
//to make black not too hard too see
|
||||
std::array<float, 4> new_color = adjust_color_for_rendering(colors[idx - 1]);
|
||||
shader->set_uniform("uniform_color", new_color);
|
||||
}
|
||||
else {
|
||||
//shader->set_uniform("uniform_color", colors[0]);
|
||||
//to make black not too hard too see
|
||||
std::array<float, 4> new_color = adjust_color_for_rendering(colors[0]);
|
||||
shader->set_uniform("uniform_color", new_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
iva.render(this->tverts_range, this->qverts_range);
|
||||
|
|
@ -913,13 +942,23 @@ void GLVolume::simple_render(GLShaderProgram* shader, ModelObjectPtrs& model_obj
|
|||
if (shader) {
|
||||
if (idx == 0) {
|
||||
int extruder_id = model_volume->extruder_id();
|
||||
shader->set_uniform("uniform_color", extruder_colors[extruder_id - 1]);
|
||||
//to make black not too hard too see
|
||||
std::array<float, 4> new_color = adjust_color_for_rendering(extruder_colors[extruder_id - 1]);
|
||||
shader->set_uniform("uniform_color", new_color);
|
||||
}
|
||||
else {
|
||||
if (idx <= extruder_colors.size())
|
||||
shader->set_uniform("uniform_color", extruder_colors[idx - 1]);
|
||||
else
|
||||
shader->set_uniform("uniform_color", extruder_colors[0]);
|
||||
if (idx <= extruder_colors.size()) {
|
||||
//shader->set_uniform("uniform_color", extruder_colors[idx - 1]);
|
||||
//to make black not too hard too see
|
||||
std::array<float, 4> new_color = adjust_color_for_rendering(extruder_colors[idx - 1]);
|
||||
shader->set_uniform("uniform_color", new_color);
|
||||
}
|
||||
else {
|
||||
//shader->set_uniform("uniform_color", extruder_colors[0]);
|
||||
//to make black not too hard too see
|
||||
std::array<float, 4> new_color = adjust_color_for_rendering(extruder_colors[0]);
|
||||
shader->set_uniform("uniform_color", new_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
iva.render(this->tverts_range, this->qverts_range);
|
||||
|
|
@ -978,8 +1017,10 @@ void GLWipeTowerVolume::render(bool with_outline) const
|
|||
|
||||
GLShaderProgram* shader = GUI::wxGetApp().get_current_shader();
|
||||
for (int i = 0; i < m_colors.size(); i++) {
|
||||
if (shader)
|
||||
shader->set_uniform("uniform_color", m_colors[i]);
|
||||
if (shader) {
|
||||
std::array<float, 4> new_color = adjust_color_for_rendering(m_colors[i]);
|
||||
shader->set_uniform("uniform_color", new_color);
|
||||
}
|
||||
this->iva_per_colors[i].render();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@
|
|||
#define glcheck()
|
||||
#endif // HAS_GLSAFE
|
||||
extern std::vector<std::array<float, 4>> get_extruders_colors();
|
||||
extern std::array<float, 4> adjust_color_for_rendering(const std::array<float, 4>& colors);
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
class SLAPrintObject;
|
||||
|
|
@ -373,7 +375,6 @@ public:
|
|||
bool is_modifier : 1;
|
||||
// Wheter or not this volume has been generated from the wipe tower
|
||||
bool is_wipe_tower : 1;
|
||||
bool is_timelapse_wipe_tower : 1;
|
||||
// Wheter or not this volume has been generated from an extrusion path
|
||||
bool is_extrusion_path : 1;
|
||||
// Wheter or not to always render this volume using its own alpha
|
||||
|
|
|
|||
|
|
@ -502,7 +502,7 @@ AuFolderPanel::AuFolderPanel(wxWindow *parent, AuxiliaryFolderType type, wxWindo
|
|||
m_button_add->SetBackgroundColor(btn_bg_white);
|
||||
m_button_add->SetBorderColor(btn_bd_white);
|
||||
m_button_add->SetMinSize(wxSize(-1, FromDIP(24)));
|
||||
m_button_add->SetCornerRadius(12);
|
||||
m_button_add->SetCornerRadius(FromDIP(12));
|
||||
m_button_add->SetFont(Label::Body_14);
|
||||
// m_button_add->Bind(wxEVT_LEFT_UP, &AuxiliaryPanel::on_add, this);
|
||||
|
||||
|
|
|
|||
|
|
@ -51,8 +51,8 @@ BBLStatusBar::BBLStatusBar(wxWindow *parent, int id)
|
|||
m_sizer->Add(m_object_info_sizer, 1, wxEXPAND | wxALL | wxALIGN_LEFT, 5);
|
||||
m_sizer->Add(m_slice_info_sizer, 1, wxEXPAND | wxALL | wxALIGN_LEFT, 5);
|
||||
m_sizer->Add(m_status_text, 1, wxEXPAND | wxALL | wxALIGN_LEFT, 5);
|
||||
m_sizer->Add(m_prog, 0, wxEXPAND | wxLEFT | wxALL | wxALIGN_RIGHT, 5);
|
||||
m_sizer->Add(m_cancelbutton, 0, wxEXPAND | wxALL | wxALIGN_RIGHT, 5);
|
||||
m_sizer->Add(m_prog, 0, wxEXPAND | wxLEFT | wxALL, 5);
|
||||
m_sizer->Add(m_cancelbutton, 0, wxEXPAND | wxALL, 5);
|
||||
m_sizer->SetSizeHints(m_self);
|
||||
m_self->SetSizer(m_sizer);
|
||||
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ BBLStatusBarSend::BBLStatusBarSend(wxWindow *parent, int id)
|
|||
m_cancelbutton->SetMinSize(wxSize(m_self->FromDIP(64), m_self->FromDIP(24)));
|
||||
m_cancelbutton->SetTextColor(wxColour(107, 107, 107));
|
||||
m_cancelbutton->SetBackgroundColor(wxColour(255, 255, 255));
|
||||
m_cancelbutton->SetCornerRadius(12);
|
||||
m_cancelbutton->SetCornerRadius(m_self->FromDIP(12));
|
||||
m_cancelbutton->Bind(wxEVT_BUTTON,
|
||||
[this](wxCommandEvent &evt) {
|
||||
m_was_cancelled = true;
|
||||
|
|
|
|||
|
|
@ -245,7 +245,7 @@ void BBLTopbar::Init(wxFrame* parent)
|
|||
this->AddStretchSpacer(1);
|
||||
|
||||
m_title_item = this->AddLabel(ID_TITLE, "", FromDIP(TOPBAR_TITLE_WIDTH));
|
||||
m_title_item->SetAlignment(wxCENTER);
|
||||
m_title_item->SetAlignment(wxALIGN_CENTRE);
|
||||
|
||||
this->AddSpacer(FromDIP(10));
|
||||
this->AddStretchSpacer(1);
|
||||
|
|
@ -407,7 +407,7 @@ void BBLTopbar::SetTitle(wxString title)
|
|||
title = wxControl::Ellipsize(title, dc, wxELLIPSIZE_END, FromDIP(TOPBAR_TITLE_WIDTH));
|
||||
|
||||
m_title_item->SetLabel(title);
|
||||
m_title_item->SetAlignment(wxALIGN_CENTRE_HORIZONTAL);
|
||||
m_title_item->SetAlignment(wxALIGN_CENTRE);
|
||||
this->Refresh();
|
||||
}
|
||||
|
||||
|
|
@ -493,7 +493,7 @@ void BBLTopbar::OnFullScreen(wxAuiToolBarEvent& event)
|
|||
m_frame->Restore();
|
||||
}
|
||||
else {
|
||||
wxDisplay display(wxDisplay::GetFromWindow(this));
|
||||
wxDisplay display(this);
|
||||
auto size = display.GetClientArea().GetSize();
|
||||
m_frame->SetMaxSize(size + wxSize{16, 16});
|
||||
m_normalRect = m_frame->GetRect();
|
||||
|
|
@ -524,7 +524,7 @@ void BBLTopbar::OnMouseLeftDClock(wxMouseEvent& mouse)
|
|||
m_frame->Restore();
|
||||
}
|
||||
else {
|
||||
wxDisplay display(wxDisplay::GetFromWindow(this));
|
||||
wxDisplay display(this);
|
||||
auto size = display.GetClientArea().GetSize();
|
||||
m_frame->SetMaxSize(size + wxSize{16, 16});
|
||||
m_normalRect = m_frame->GetRect();
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ namespace GUI {
|
|||
|
||||
m_panel_left = new StaticBox(this, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(201), FromDIP(212)), wxBORDER_NONE);
|
||||
m_panel_left->SetMinSize(wxSize(FromDIP(201), FromDIP(212)));
|
||||
m_panel_left->SetCornerRadius(8);
|
||||
m_panel_left->SetCornerRadius(FromDIP(8));
|
||||
m_panel_left->SetBackgroundColor(BIND_DIALOG_GREY200);
|
||||
wxBoxSizer *m_sizere_left_h = new wxBoxSizer(wxHORIZONTAL);
|
||||
wxBoxSizer *m_sizere_left_v= new wxBoxSizer(wxVERTICAL);
|
||||
|
|
@ -55,7 +55,7 @@ namespace GUI {
|
|||
|
||||
m_panel_right = new StaticBox(this, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(201), FromDIP(212)), wxBORDER_NONE);
|
||||
m_panel_right->SetMinSize(wxSize(FromDIP(201), FromDIP(212)));
|
||||
m_panel_right->SetCornerRadius(8);
|
||||
m_panel_right->SetCornerRadius(FromDIP(8));
|
||||
m_panel_right->SetBackgroundColor(BIND_DIALOG_GREY200);
|
||||
|
||||
m_user_name = new wxStaticText(m_panel_right, wxID_ANY, wxEmptyString);
|
||||
|
|
@ -136,7 +136,7 @@ namespace GUI {
|
|||
m_button_bind->SetTextColor(*wxWHITE);
|
||||
m_button_bind->SetSize(BIND_DIALOG_BUTTON_SIZE);
|
||||
m_button_bind->SetMinSize(BIND_DIALOG_BUTTON_SIZE);
|
||||
m_button_bind->SetCornerRadius(10);
|
||||
m_button_bind->SetCornerRadius(FromDIP(12));
|
||||
|
||||
|
||||
StateColor btn_bg_white(std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Hovered),
|
||||
|
|
@ -148,7 +148,7 @@ namespace GUI {
|
|||
m_button_cancel->SetSize(BIND_DIALOG_BUTTON_SIZE);
|
||||
m_button_cancel->SetMinSize(BIND_DIALOG_BUTTON_SIZE);
|
||||
m_button_cancel->SetTextColor(BIND_DIALOG_GREY900);
|
||||
m_button_cancel->SetCornerRadius(10);
|
||||
m_button_cancel->SetCornerRadius(FromDIP(12));
|
||||
|
||||
m_sizer_button->Add(m_button_bind, 0, wxALIGN_CENTER, 0);
|
||||
m_sizer_button->Add(0, 0, 0, wxLEFT, FromDIP(13));
|
||||
|
|
@ -273,7 +273,7 @@ UnBindMachineDilaog::UnBindMachineDilaog(Plater *plater /*= nullptr*/)
|
|||
|
||||
auto m_panel_left = new StaticBox(this, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(201), FromDIP(212)), wxBORDER_NONE);
|
||||
m_panel_left->SetMinSize(wxSize(FromDIP(201), FromDIP(212)));
|
||||
m_panel_left->SetCornerRadius(8);
|
||||
m_panel_left->SetCornerRadius(FromDIP(8));
|
||||
m_panel_left->SetBackgroundColor(BIND_DIALOG_GREY200);
|
||||
wxBoxSizer *m_sizere_left_h = new wxBoxSizer(wxHORIZONTAL);
|
||||
wxBoxSizer *m_sizere_left_v= new wxBoxSizer(wxVERTICAL);
|
||||
|
|
@ -297,7 +297,7 @@ UnBindMachineDilaog::UnBindMachineDilaog(Plater *plater /*= nullptr*/)
|
|||
|
||||
auto m_panel_right = new StaticBox(this, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(201), FromDIP(212)), wxBORDER_NONE);
|
||||
m_panel_right->SetMinSize(wxSize(FromDIP(201), FromDIP(212)));
|
||||
m_panel_right->SetCornerRadius(8);
|
||||
m_panel_right->SetCornerRadius(FromDIP(8));
|
||||
m_panel_right->SetBackgroundColor(BIND_DIALOG_GREY200);
|
||||
m_user_name = new wxStaticText(m_panel_right, wxID_ANY, wxEmptyString);
|
||||
m_user_name->SetBackgroundColour(BIND_DIALOG_GREY200);
|
||||
|
|
@ -374,7 +374,7 @@ UnBindMachineDilaog::UnBindMachineDilaog(Plater *plater /*= nullptr*/)
|
|||
m_button_unbind->SetTextColor(*wxWHITE);
|
||||
m_button_unbind->SetSize(BIND_DIALOG_BUTTON_SIZE);
|
||||
m_button_unbind->SetMinSize(BIND_DIALOG_BUTTON_SIZE);
|
||||
m_button_unbind->SetCornerRadius(10);
|
||||
m_button_unbind->SetCornerRadius(FromDIP(12));
|
||||
|
||||
|
||||
StateColor btn_bg_white(std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Hovered),
|
||||
|
|
@ -386,7 +386,7 @@ UnBindMachineDilaog::UnBindMachineDilaog(Plater *plater /*= nullptr*/)
|
|||
m_button_cancel->SetSize(BIND_DIALOG_BUTTON_SIZE);
|
||||
m_button_cancel->SetMinSize(BIND_DIALOG_BUTTON_SIZE);
|
||||
m_button_cancel->SetTextColor(BIND_DIALOG_GREY900);
|
||||
m_button_cancel->SetCornerRadius(10);
|
||||
m_button_cancel->SetCornerRadius(FromDIP(12));
|
||||
|
||||
m_sizer_button->Add(m_button_unbind, 0, wxALIGN_CENTER, 0);
|
||||
m_sizer_button->Add(0, 0, 0, wxLEFT, FromDIP(13));
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ CalibrationDialog::CalibrationDialog(Plater *plater)
|
|||
|
||||
calibration_panel->SetSizer(calibration_sizer);
|
||||
calibration_panel->Layout();
|
||||
calibration_sizer->Add(m_calibration_flow, 0, wxALIGN_CENTER_HORIZONTAL | wxEXPAND, 0);
|
||||
calibration_sizer->Add(m_calibration_flow, 0, wxEXPAND, 0);
|
||||
|
||||
StateColor btn_bg_green(std::pair<wxColour, int>(AMS_CONTROL_DISABLE_COLOUR, StateColor::Disabled), std::pair<wxColour, int>(wxColour(27, 136, 68), StateColor::Pressed),
|
||||
std::pair<wxColour, int>(wxColour(61, 203, 115), StateColor::Hovered), std::pair<wxColour, int>(AMS_CONTROL_BRAND_COLOUR, StateColor::Normal));
|
||||
|
|
@ -138,7 +138,7 @@ CalibrationDialog::CalibrationDialog(Plater *plater)
|
|||
cali_right_panel->SetSizer(cali_right_sizer_h);
|
||||
cali_right_panel->Layout();
|
||||
|
||||
sizer_body->Add(cali_right_panel, 0, wxALIGN_CENTER_HORIZONTAL | wxEXPAND, 0);
|
||||
sizer_body->Add(cali_right_panel, 0, wxEXPAND, 0);
|
||||
|
||||
body_panel->SetSizer(sizer_body);
|
||||
body_panel->Layout();
|
||||
|
|
|
|||
|
|
@ -209,6 +209,18 @@ bool HMSItem::parse_hms_info(unsigned attr, unsigned code)
|
|||
return result;
|
||||
}
|
||||
|
||||
std::string HMSItem::get_long_error_code()
|
||||
{
|
||||
char buf[64];
|
||||
::sprintf(buf, "%02X%02X%02X00000%1X%04X",
|
||||
this->module_id,
|
||||
this->module_num,
|
||||
this->part_id,
|
||||
(int)this->msg_level,
|
||||
this->msg_code);
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
wxString HMSItem::get_module_name(ModuleID module_id)
|
||||
{
|
||||
switch (module_id)
|
||||
|
|
|
|||
|
|
@ -240,6 +240,8 @@ public:
|
|||
HMSMessageLevel msg_level = HMS_UNKNOWN;
|
||||
int msg_code = 0;
|
||||
bool parse_hms_info(unsigned attr, unsigned code);
|
||||
std::string get_long_error_code();
|
||||
|
||||
static wxString get_module_name(ModuleID module_id);
|
||||
static wxString get_hms_msg_level_str(HMSMessageLevel level);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ DownloadProgressDialog::DownloadProgressDialog(wxString title)
|
|||
m_panel_download->SetSize(wxSize(FromDIP(340), -1));
|
||||
m_panel_download->SetMinSize(wxSize(FromDIP(340), -1));
|
||||
m_panel_download->SetMaxSize(wxSize(FromDIP(340), -1));
|
||||
m_sizer_main->Add(m_panel_download, 0, wxALIGN_CENTER_VERTICAL|wxALL, FromDIP(20));
|
||||
m_sizer_main->Add(m_panel_download, 0, wxALL, FromDIP(20));
|
||||
m_sizer_main->Add(0, 0, 1, wxBOTTOM, 10);
|
||||
|
||||
SetSizer(m_sizer_main);
|
||||
|
|
|
|||
|
|
@ -171,7 +171,10 @@ wxSize BitmapTextRenderer::GetSize() const
|
|||
}
|
||||
else
|
||||
#endif // SUPPORTS_MARKUP && wxHAS_GENERIC_DATAVIEWCTRL
|
||||
{
|
||||
size = GetTextExtent(m_value.GetText());
|
||||
size.x = size.x * 9 / 8;
|
||||
}
|
||||
|
||||
if (m_value.GetBitmap().IsOk())
|
||||
size.x += m_value.GetBitmap().GetWidth() + 4;
|
||||
|
|
|
|||
|
|
@ -3000,7 +3000,8 @@ void GCodeViewer::load_shells(const Print& print, bool initialized, bool force_p
|
|||
|
||||
const double max_z = print.objects()[0]->model_object()->get_model()->bounding_box().max(2);
|
||||
const PrintConfig& config = print.config();
|
||||
if (extruders_count > 1 && config.enable_prime_tower && (config.print_sequence == PrintSequence::ByLayer)) {
|
||||
if (print.enable_timelapse_print()
|
||||
|| (extruders_count > 1 && config.enable_prime_tower && (config.print_sequence == PrintSequence::ByLayer))) {
|
||||
const float depth = print.wipe_tower_data(extruders_count).depth;
|
||||
const float brim_width = print.wipe_tower_data(extruders_count).brim_width;
|
||||
|
||||
|
|
@ -3063,8 +3064,10 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
|
|||
case EViewType::ColorPrint: {
|
||||
if (path.cp_color_id >= static_cast<unsigned char>(m_tools.m_tool_colors.size()))
|
||||
color = { 0.5f, 0.5f, 0.5f, 1.0f };
|
||||
else
|
||||
else {
|
||||
color = m_tools.m_tool_colors[path.cp_color_id];
|
||||
color = adjust_color_for_rendering(color);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EViewType::FilamentId: {
|
||||
|
|
@ -3946,6 +3949,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
|
|||
//BBS: GUI refactor: move to the right
|
||||
imgui.set_next_window_pos(float(canvas_width - right_margin * m_scale), 0.0f, ImGuiCond_Always, 1.0f, 0.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0,0.0));
|
||||
ImGui::PushStyleColor(ImGuiCol_Separator, ImVec4(1.0f,1.0f,1.0f,0.6f));
|
||||
ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.00f, 0.68f, 0.26f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.00f, 0.68f, 0.26f, 1.0f));
|
||||
|
|
@ -3979,8 +3983,15 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
|
|||
const float percent_bar_size = 0;
|
||||
|
||||
bool imperial_units = wxGetApp().app_config->get("use_inches") == "1";
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
ImVec2 pos_rect = ImGui::GetCursorScreenPos();
|
||||
float window_padding = 4.0f * m_scale;
|
||||
|
||||
auto append_item = [icon_size, percent_bar_size, &imgui, imperial_units](EItemType type, const Color &color, const std::string &label,
|
||||
draw_list->AddRectFilled(ImVec2(pos_rect.x,pos_rect.y - ImGui::GetStyle().WindowPadding.y),
|
||||
ImVec2(pos_rect.x + ImGui::GetWindowWidth() + ImGui::GetFrameHeight(),pos_rect.y + ImGui::GetFrameHeight() + window_padding * 2.5),
|
||||
ImGui::GetColorU32(ImVec4(0,0,0,0.3)));
|
||||
|
||||
auto append_item = [icon_size, percent_bar_size, &imgui, imperial_units,&window_padding,&draw_list,this](EItemType type, const Color &color, const std::string &label,
|
||||
bool visible = true, const std::string& time = "", float percent = 0.0f, float max_percent = 0.0f, const std::array<float, 4>& offsets = { 0.0f, 0.0f, 0.0f, 0.0f },
|
||||
double used_filament_m = 0.0, double used_filament_g = 0.0,
|
||||
std::function<void()> callback = nullptr) {
|
||||
|
|
@ -3989,13 +4000,13 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
|
|||
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f);
|
||||
*/
|
||||
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
ImVec2 pos = ImGui::GetCursorScreenPos();
|
||||
float dummy_size = icon_size;
|
||||
ImVec2 pos = ImVec2(ImGui::GetCursorScreenPos().x + window_padding * 3, ImGui::GetCursorScreenPos().y);
|
||||
float dummy_size = icon_size * m_scale;
|
||||
|
||||
switch (type) {
|
||||
default:
|
||||
case EItemType::Rect: {
|
||||
draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 5.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size + 3.0f },
|
||||
draw_list->AddRectFilled({ pos.x + 1.0f * m_scale, pos.y + 3.0f * m_scale }, { pos.x + icon_size - 1.0f * m_scale, pos.y + icon_size + 1.0f * m_scale },
|
||||
ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }));
|
||||
break;
|
||||
}
|
||||
|
|
@ -4019,10 +4030,18 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
|
|||
}
|
||||
|
||||
// draw text
|
||||
ImGui::Dummy({ dummy_size, dummy_size });
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(20.0, 6.0 * m_scale));
|
||||
ImGui::Dummy({ dummy_size, 0.0 });
|
||||
ImGui::SameLine();
|
||||
if (callback != nullptr) {
|
||||
if (ImGui::MenuItem(label.c_str()))
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f * m_scale);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20.0 * m_scale,0.0));
|
||||
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(1.00f, 0.68f, 0.26f, 0.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_BorderActive, ImVec4(0.00f, 0.68f, 0.26f, 1.00f));
|
||||
bool b_menu_item = ImGui::BBLMenuItem(label.c_str());
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopStyleColor(2);
|
||||
if (b_menu_item)
|
||||
callback();
|
||||
else {
|
||||
// show tooltip
|
||||
|
|
@ -4111,6 +4130,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
|
|||
imgui.text(buf);
|
||||
}*/
|
||||
}
|
||||
ImGui::PopStyleVar(1);
|
||||
|
||||
/* BBS GUI refactor */
|
||||
/*if (!visible)
|
||||
|
|
@ -4143,10 +4163,10 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
|
|||
auto append_headers = [&imgui](const std::array<std::string, 5>& texts, const std::array<float, 4>& offsets) {
|
||||
size_t i = 0;
|
||||
for (; i < offsets.size(); i++) {
|
||||
imgui.text(texts[i]);
|
||||
imgui.bold_text(texts[i]);
|
||||
ImGui::SameLine(offsets[i]);
|
||||
}
|
||||
imgui.text(texts[i]);
|
||||
imgui.bold_text(texts[i]);
|
||||
ImGui::Separator();
|
||||
};
|
||||
|
||||
|
|
@ -4230,6 +4250,9 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
|
|||
};
|
||||
|
||||
//BBS display Color Scheme
|
||||
ImGui::Dummy({ window_padding, window_padding });
|
||||
ImGui::Dummy({ window_padding, window_padding });
|
||||
ImGui::SameLine();
|
||||
std::wstring btn_name;
|
||||
if (m_fold)
|
||||
btn_name = ImGui::UnfoldButtonIcon + boost::nowide::widen(std::string(""));
|
||||
|
|
@ -4247,10 +4270,9 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
|
|||
ImGui::PopStyleColor(3);
|
||||
ImGui::PopStyleVar(1);
|
||||
ImGui::SameLine();
|
||||
ImGui::Text(_u8L("Color Scheme").c_str());
|
||||
imgui.bold_text(_u8L("Color Scheme"));
|
||||
push_combo_style();
|
||||
float combo_width = ImGui::GetContentRegionAvailWidth() - ImGui::CalcTextSize(_u8L("Color Scheme").c_str()).x - button_width - ImGui::GetStyle().FramePadding.x * 4;
|
||||
ImGui::PushItemWidth(combo_width);
|
||||
|
||||
ImGui::SameLine();
|
||||
const char* view_type_value = view_type_items_str[m_view_type_sel].c_str();
|
||||
ImGuiComboFlags flags = 0;
|
||||
|
|
@ -4276,11 +4298,13 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
|
|||
ImGui::EndCombo();
|
||||
}
|
||||
pop_combo_style();
|
||||
ImGui::SameLine();
|
||||
ImGui::Dummy({ window_padding, window_padding });
|
||||
|
||||
if (m_fold) {
|
||||
imgui.end();
|
||||
ImGui::PopStyleColor(6);
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopStyleVar(2);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -4355,6 +4379,8 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
|
|||
}
|
||||
|
||||
// extrusion paths section -> title
|
||||
ImGui::Dummy({ window_padding, window_padding });
|
||||
ImGui::SameLine();
|
||||
switch (m_view_type)
|
||||
{
|
||||
case EViewType::FeatureType:
|
||||
|
|
@ -4467,6 +4493,8 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
|
|||
case EViewType::Feedrate: {
|
||||
append_range(m_extrusions.ranges.feedrate, 0);
|
||||
ImGui::Spacing();
|
||||
ImGui::Dummy({ window_padding, window_padding });
|
||||
ImGui::SameLine();
|
||||
append_headers({_u8L("Options"), "", "", "", _u8L("Display")}, offsets);
|
||||
const bool travel_visible = m_buffers[buffer_id(EMoveType::Travel)].visible;
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 3.0f));
|
||||
|
|
@ -4596,6 +4624,8 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
|
|||
max_len += std::max(ImGui::CalcTextSize(filament_change_str.c_str()).x, ImGui::CalcTextSize(flushed_filament_str.c_str()).x);
|
||||
//BBS: display total flushed filament
|
||||
{
|
||||
ImGui::Dummy({window_padding, window_padding});
|
||||
ImGui::SameLine();
|
||||
imgui.text(flushed_filament_str + ":");
|
||||
ImGui::SameLine(max_len);
|
||||
char buf[64];
|
||||
|
|
@ -4607,6 +4637,8 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
|
|||
}
|
||||
//BBS display filament change times
|
||||
{
|
||||
ImGui::Dummy({window_padding, window_padding});
|
||||
ImGui::SameLine();
|
||||
imgui.text(filament_change_str + ":");
|
||||
ImGui::SameLine(max_len);
|
||||
char temp_buf[64];
|
||||
|
|
@ -4962,6 +4994,8 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
|
|||
}
|
||||
}
|
||||
ImGui::Dummy(ImVec2(0.0f, ImGui::GetFontSize() * 0.1));
|
||||
ImGui::Dummy({ window_padding, window_padding });
|
||||
ImGui::SameLine();
|
||||
imgui.title(time_title);
|
||||
std::string filament_str = _u8L("Filament");
|
||||
std::string prepare_str = _u8L("Prepare time");
|
||||
|
|
@ -4976,6 +5010,8 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
|
|||
}
|
||||
|
||||
//BBS display filament cost
|
||||
ImGui::Dummy({ window_padding, window_padding });
|
||||
ImGui::SameLine();
|
||||
imgui.text(filament_str + ":");
|
||||
ImGui::SameLine(max_len);
|
||||
|
||||
|
|
@ -4997,13 +5033,19 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
|
|||
};
|
||||
//BBS: start gcode is prepeare time
|
||||
if (role_time(erCustom) != 0.0f) {
|
||||
ImGui::Dummy({ window_padding, window_padding });
|
||||
ImGui::SameLine();
|
||||
imgui.text(prepare_str + ":");
|
||||
ImGui::SameLine(max_len);
|
||||
imgui.text(short_time(get_time_dhms(role_time(erCustom))));
|
||||
}
|
||||
ImGui::Dummy({ window_padding, window_padding });
|
||||
ImGui::SameLine();
|
||||
imgui.text(print_str + ":");
|
||||
ImGui::SameLine(max_len);
|
||||
imgui.text(short_time(get_time_dhms(time_mode.time - role_time(erCustom))));
|
||||
ImGui::Dummy({ window_padding, window_padding });
|
||||
ImGui::SameLine();
|
||||
imgui.text(total_str + ":");
|
||||
ImGui::SameLine(max_len);
|
||||
imgui.text(short_time(get_time_dhms(time_mode.time)));
|
||||
|
|
@ -5035,16 +5077,17 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
|
|||
}
|
||||
}
|
||||
legend_height = ImGui::GetCurrentWindow()->Size.y;
|
||||
|
||||
ImGui::Dummy({ window_padding, window_padding});
|
||||
imgui.end();
|
||||
ImGui::PopStyleColor(6);
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopStyleVar(2);
|
||||
}
|
||||
|
||||
void GCodeViewer::push_combo_style()
|
||||
{
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.0,8.0));
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.3f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.0f, 0.0f, 0.0f, 0.3f));
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.0f, 0.0f, 0.0f, 0.3f));
|
||||
|
|
@ -5056,7 +5099,7 @@ void GCodeViewer::push_combo_style()
|
|||
}
|
||||
void GCodeViewer::pop_combo_style()
|
||||
{
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopStyleVar(3);
|
||||
ImGui::PopStyleColor(8);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -75,8 +75,7 @@ static constexpr const float TRACKBALLSIZE = 0.8f;
|
|||
static const float SLIDER_DEFAULT_RIGHT_MARGIN = 10.0f;
|
||||
static const float SLIDER_DEFAULT_BOTTOM_MARGIN = 10.0f;
|
||||
static const float SLIDER_RIGHT_MARGIN = 105.0f;
|
||||
static const float SLIDER_BOTTOM_MARGIN = 65.0f;
|
||||
|
||||
static const float SLIDER_BOTTOM_MARGIN = 90.0f;
|
||||
|
||||
float GLCanvas3D::DEFAULT_BG_LIGHT_COLOR[3] = { 0.906f, 0.906f, 0.906f };
|
||||
float GLCanvas3D::ERROR_BG_LIGHT_COLOR[3] = { 0.753f, 0.192f, 0.039f };
|
||||
|
|
@ -1926,7 +1925,11 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
|||
bool wt = dynamic_cast<const ConfigOptionBool*>(m_config->option("enable_prime_tower"))->value;
|
||||
auto co = dynamic_cast<const ConfigOptionEnum<PrintSequence>*>(m_config->option<ConfigOptionEnum<PrintSequence>>("print_sequence"));
|
||||
|
||||
if (filaments_count > 1 && wt && co != nullptr && co->value != PrintSequence::ByObject) {
|
||||
const DynamicPrintConfig &dconfig = wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||
const ConfigOption * option = dconfig.option("timelapse_no_toolhead");
|
||||
bool timelapse_enabled = option ? option->getBool() : false;
|
||||
|
||||
if (timelapse_enabled || (filaments_count > 1 && wt && co != nullptr && co->value != PrintSequence::ByObject)) {
|
||||
for (int plate_id = 0; plate_id < n_plates; plate_id++) {
|
||||
DynamicPrintConfig& proj_cfg = wxGetApp().preset_bundle->project_config;
|
||||
float x = dynamic_cast<const ConfigOptionFloats*>(proj_cfg.option("wipe_tower_x"))->get_at(plate_id);
|
||||
|
|
@ -3828,32 +3831,6 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type)
|
|||
}
|
||||
}
|
||||
|
||||
#if 1
|
||||
// BBS: update Timelapse Wipe Tower according to max height
|
||||
for (unsigned int obj_idx = 0; obj_idx < (unsigned int) m_model->objects.size(); ++obj_idx) {
|
||||
ModelObject *model_object = m_model->objects[obj_idx];
|
||||
if (model_object->is_timelapse_wipe_tower) {
|
||||
for (GLVolume *volume : m_volumes.volumes) {
|
||||
if (volume->composite_id.object_id == obj_idx) {
|
||||
int instance_idx = volume->instance_idx();
|
||||
auto curr_plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
|
||||
double max_height = curr_plate->estimate_timelapse_wipe_tower_height();
|
||||
float z_factor = max_height / model_object->raw_mesh_bounding_box().size()[2];
|
||||
volume->set_instance_scaling_factor(Vec3d(1.0, 1.0, z_factor));
|
||||
model_object->instances[instance_idx]->set_scaling_factor(Vec3d(1.0, 1.0, z_factor));
|
||||
volume->is_timelapse_wipe_tower = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ensure_on_bed(obj_idx, false);
|
||||
model_object->invalidate_bounding_box();
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
//BBS: notify instance updates to part plater list
|
||||
m_selection.notify_instance_update(-1, -1);
|
||||
|
||||
|
|
@ -5869,6 +5846,9 @@ void GLCanvas3D::_check_and_update_toolbar_icon_scale()
|
|||
m_assemble_view_toolbar.set_scale(sc);
|
||||
collapse_toolbar.set_scale(sc);
|
||||
size *= m_retina_helper->get_scale_factor();
|
||||
|
||||
auto *m_notification = wxGetApp().plater()->get_notification_manager();
|
||||
m_notification->set_scale(sc);
|
||||
#else
|
||||
//BBS: GUI refactor: GLToolbar
|
||||
m_main_toolbar.set_icons_size(GLGizmosManager::Default_Icons_Size * scale);
|
||||
|
|
|
|||
|
|
@ -805,8 +805,8 @@ void GLToolbar::do_action(GLToolbarItem::EActionType type, int item_id, GLCanvas
|
|||
|
||||
if ((m_type == Normal) && (item->get_state() != GLToolbarItem::Disabled))
|
||||
{
|
||||
// the item may get disabled during the action, if not, set it back to hover state
|
||||
item->set_state(GLToolbarItem::Hover);
|
||||
// the item may get disabled during the action, if not, set it back to normal state
|
||||
item->set_state(GLToolbarItem::Normal);
|
||||
parent.render();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1089,6 +1089,12 @@ void GUI_App::post_init()
|
|||
mainframe->refresh_plugin_tips();
|
||||
});
|
||||
|
||||
// update hms info
|
||||
CallAfter([this] {
|
||||
if (hms_query)
|
||||
hms_query->check_hms_info();
|
||||
});
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "finished post_init";
|
||||
//BBS: remove the single instance currently
|
||||
/*#ifdef _WIN32
|
||||
|
|
@ -1111,6 +1117,7 @@ GUI_App::GUI_App()
|
|||
, m_app_mode(EAppMode::Editor)
|
||||
, m_em_unit(10)
|
||||
, m_imgui(new ImGuiWrapper())
|
||||
, hms_query(new HMSQuery())
|
||||
//, m_removable_drive_manager(std::make_unique<RemovableDriveManager>())
|
||||
//, m_other_instance_message_handler(std::make_unique<OtherInstanceMessageHandler>())
|
||||
{
|
||||
|
|
@ -2203,6 +2210,7 @@ __retry:
|
|||
auto bambu_source = Slic3r::NetworkAgent::get_bambu_source_entry();
|
||||
if (!bambu_source) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": can not get bambu source module!";
|
||||
m_networking_compatible = false;
|
||||
if (app_config->get("installed_networking") == "1") {
|
||||
m_networking_need_update = true;
|
||||
}
|
||||
|
|
@ -2610,6 +2618,10 @@ void GUI_App::recreate_GUI(const wxString& msg_name)
|
|||
obj_list()->set_min_height();
|
||||
update_mode();
|
||||
|
||||
//check hms info for different language
|
||||
if (hms_query)
|
||||
hms_query->check_hms_info();
|
||||
|
||||
//BBS: trigger restore project logic here, and skip confirm
|
||||
plater_->trigger_restore_project(1);
|
||||
|
||||
|
|
@ -3019,10 +3031,12 @@ std::string GUI_App::handle_web_request(std::string cmd)
|
|||
wxKeyEvent e(wxEVT_CHAR_HOOK);
|
||||
#ifdef __APPLE__
|
||||
e.SetControlDown(cmdKey);
|
||||
e.SetRawControlDown(ctrlKey);
|
||||
#else
|
||||
e.SetControlDown(ctrlKey);
|
||||
#endif
|
||||
e.SetShiftDown(shiftKey);
|
||||
keyCode = keyCode == 188 ? ',' : keyCode;
|
||||
e.m_keyCode = keyCode;
|
||||
e.SetEventObject(mainframe);
|
||||
wxPostEvent(mainframe, e);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#include "slic3r/GUI/DeviceManager.hpp"
|
||||
#include "slic3r/Utils/NetworkAgent.hpp"
|
||||
#include "slic3r/GUI/WebViewDialog.hpp"
|
||||
#include "slic3r/GUI/HMS.hpp"
|
||||
#include "slic3r/GUI/Jobs/UpgradeNetworkJob.hpp"
|
||||
#include "../Utils/PrintHost.hpp"
|
||||
|
||||
|
|
@ -62,6 +63,7 @@ class ParamsPanel;
|
|||
class NotificationManager;
|
||||
struct GUI_InitParams;
|
||||
class ParamsDialog;
|
||||
class HMSQuery;
|
||||
|
||||
|
||||
enum FileType
|
||||
|
|
@ -264,6 +266,7 @@ private:
|
|||
std::shared_ptr<UpgradeNetworkJob> m_upgrade_network_job;
|
||||
|
||||
VersionInfo version_info;
|
||||
HMSQuery *hms_query { nullptr };
|
||||
|
||||
boost::thread m_sync_update_thread;
|
||||
bool enable_sync = false;
|
||||
|
|
@ -280,6 +283,7 @@ public:
|
|||
void show_message_box(std::string msg) { wxMessageBox(msg); }
|
||||
EAppMode get_app_mode() const { return m_app_mode; }
|
||||
Slic3r::DeviceManager* getDeviceManager() { return m_device_manager; }
|
||||
HMSQuery* get_hms_query() { return hms_query; }
|
||||
NetworkAgent* getAgent() { return m_agent; }
|
||||
bool is_editor() const { return m_app_mode == EAppMode::Editor; }
|
||||
bool is_gcode_viewer() const { return m_app_mode == EAppMode::GCodeViewer; }
|
||||
|
|
|
|||
|
|
@ -459,15 +459,6 @@ wxMenu* MenuFactory::append_submenu_add_generic(wxMenu* menu, ModelVolumeType ty
|
|||
[type, item](wxCommandEvent&) { obj_list()->load_generic_subobject(item, type); }, "", menu);
|
||||
}
|
||||
|
||||
// BBS: only add timelapse tower item for plate
|
||||
if (type == ModelVolumeType::INVALID) {
|
||||
auto item = L("Timelapse Wipe Tower");
|
||||
type = ModelVolumeType::TIMELAPSE_WIPE_TOWER;
|
||||
append_menu_item(sub_menu, wxID_ANY, _(item), "",
|
||||
[type, item](wxCommandEvent &) { obj_list()->load_generic_subobject(item, type); }, "", menu,
|
||||
[]() { return plater()->can_add_timelapse_wt(); }, m_parent);
|
||||
}
|
||||
|
||||
return sub_menu;
|
||||
}
|
||||
|
||||
|
|
@ -1392,8 +1383,8 @@ void MenuFactory::append_menu_item_locked(wxMenu* menu)
|
|||
m_parent->Bind(wxEVT_UPDATE_UI, [](wxUpdateUIEvent& evt) {
|
||||
PartPlate* plate = plater()->get_partplate_list().get_selected_plate();
|
||||
assert(plate);
|
||||
bool check = plate->is_locked();
|
||||
evt.Check(check);
|
||||
//bool check = plate->is_locked();
|
||||
//evt.Check(check);
|
||||
plater()->set_current_canvas_as_dirty();
|
||||
}, item->GetId());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
#include <wx/progdlg.h>
|
||||
#include <wx/listbook.h>
|
||||
#include <wx/numformatter.h>
|
||||
#include <wx/headerctrl.h>
|
||||
|
||||
#include "slic3r/Utils/FixModelByWin10.hpp"
|
||||
#include "libslic3r/Format/bbs_3mf.hpp"
|
||||
|
|
@ -75,7 +76,11 @@ ObjectList::ObjectList(wxWindow* parent) :
|
|||
wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_MULTIPLE)
|
||||
{
|
||||
wxGetApp().UpdateDVCDarkUI(this, true);
|
||||
|
||||
SetFont(Label::sysFont(13));
|
||||
#ifdef __WXMSW__
|
||||
GenericGetHeader()->SetFont(Label::sysFont(13));
|
||||
#endif
|
||||
|
||||
// create control
|
||||
create_objects_ctrl();
|
||||
|
||||
|
|
@ -1864,7 +1869,7 @@ static TriangleMesh create_mesh(const std::string& type_name, const BoundingBoxf
|
|||
const double side = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.1);
|
||||
|
||||
indexed_triangle_set mesh;
|
||||
if (type_name == "Cube" || type_name == "Timelapse Wipe Tower")
|
||||
if (type_name == "Cube")
|
||||
// Sitting on the print bed, left front front corner at (0, 0).
|
||||
mesh = its_make_cube(side, side, side);
|
||||
else if (type_name == "Cylinder")
|
||||
|
|
@ -1892,10 +1897,6 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode
|
|||
load_shape_object(type_name);
|
||||
return;
|
||||
}
|
||||
else if (type == ModelVolumeType::TIMELAPSE_WIPE_TOWER) {
|
||||
load_shape_object(type_name, true);
|
||||
return;
|
||||
}
|
||||
|
||||
const int obj_idx = get_selected_obj_idx();
|
||||
if (obj_idx < 0)
|
||||
|
|
@ -1999,7 +2000,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode
|
|||
}
|
||||
}
|
||||
|
||||
void ObjectList::load_shape_object(const std::string &type_name, bool is_timelapse_wt)
|
||||
void ObjectList::load_shape_object(const std::string &type_name)
|
||||
{
|
||||
const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
|
||||
//assert(selection.get_object_idx() == -1); // Add nothing is something is selected on 3DScene
|
||||
|
|
@ -2016,11 +2017,11 @@ void ObjectList::load_shape_object(const std::string &type_name, bool is_timelap
|
|||
BoundingBoxf3 bb;
|
||||
TriangleMesh mesh = create_mesh(type_name, bb);
|
||||
// BBS: remove "Shape" prefix
|
||||
load_mesh_object(mesh, _(type_name), true, is_timelapse_wt);
|
||||
load_mesh_object(mesh, _(type_name));
|
||||
wxGetApp().mainframe->update_title();
|
||||
}
|
||||
|
||||
void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name, bool center, bool is_timelapse_wt)
|
||||
void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name, bool center)
|
||||
{
|
||||
// Add mesh to model as a new object
|
||||
Model& model = wxGetApp().plater()->model();
|
||||
|
|
@ -2044,27 +2045,11 @@ void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name
|
|||
new_object->invalidate_bounding_box();
|
||||
new_object->translate(-bb.center());
|
||||
|
||||
if (is_timelapse_wt) {
|
||||
new_object->is_timelapse_wipe_tower = true;
|
||||
auto curr_plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
|
||||
int highest_extruder = 0;
|
||||
double max_height = curr_plate->estimate_timelapse_wipe_tower_height(&highest_extruder);
|
||||
new_object->scale(1, 1, max_height / new_object->bounding_box().size()[2]);
|
||||
// BBS: find an empty cell to put the copied object
|
||||
auto start_point = wxGetApp().plater()->build_volume().bounding_volume2d().center();
|
||||
auto empty_cell = wxGetApp().plater()->canvas3D()->get_nearest_empty_cell({start_point(0), start_point(1)});
|
||||
|
||||
// move to garbage bin of curr plate
|
||||
auto offset = curr_plate->get_origin() + Vec3d(80.0, 230.0, -new_object->origin_translation.z());
|
||||
new_object->instances[0]->set_offset(offset);
|
||||
|
||||
new_object->config.set_key_value("sparse_infill_density", new ConfigOptionPercent(0));
|
||||
new_object->config.set_key_value("top_shell_layers", new ConfigOptionInt(0));
|
||||
new_object->config.set("extruder", highest_extruder);
|
||||
} else {
|
||||
// BBS: find an empty cell to put the copied object
|
||||
auto start_point = wxGetApp().plater()->build_volume().bounding_volume2d().center();
|
||||
auto empty_cell = wxGetApp().plater()->canvas3D()->get_nearest_empty_cell({start_point(0), start_point(1)});
|
||||
|
||||
new_object->instances[0]->set_offset(center ? to_3d(Vec2d(empty_cell(0), empty_cell(1)), -new_object->origin_translation.z()) : bb.center());
|
||||
}
|
||||
new_object->instances[0]->set_offset(center ? to_3d(Vec2d(empty_cell(0), empty_cell(1)), -new_object->origin_translation.z()) : bb.center());
|
||||
|
||||
new_object->ensure_on_bed();
|
||||
|
||||
|
|
@ -2760,11 +2745,6 @@ bool ObjectList::can_merge_to_multipart_object() const
|
|||
for (wxDataViewItem item : sels) {
|
||||
if (!(m_objects_model->GetItemType(item) & (itObject | itInstance)))
|
||||
return false;
|
||||
|
||||
// BBS: do not support to merge timelapse wipe tower with other objects
|
||||
ObjectDataViewModelNode* node = static_cast<ObjectDataViewModelNode*>(item.GetID());
|
||||
if (node != nullptr && node->IsTimelapseWipeTower())
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -279,8 +279,8 @@ public:
|
|||
//void load_part(ModelObject& model_object, std::vector<ModelVolume*>& added_volumes, ModelVolumeType type, bool from_galery = false);
|
||||
void load_modifier(const wxArrayString& input_files, ModelObject& model_object, std::vector<ModelVolume*>& added_volumes, ModelVolumeType type, bool from_galery = false);
|
||||
void load_generic_subobject(const std::string& type_name, const ModelVolumeType type);
|
||||
void load_shape_object(const std::string &type_name, bool is_timelapse_wt = false);
|
||||
void load_mesh_object(const TriangleMesh &mesh, const wxString &name, bool center = true, bool is_timelapse_wt = false);
|
||||
void load_shape_object(const std::string &type_name);
|
||||
void load_mesh_object(const TriangleMesh &mesh, const wxString &name, bool center = true);
|
||||
void del_object(const int obj_idx, bool refresh_immediately = true);
|
||||
void del_subobject_item(wxDataViewItem& item);
|
||||
void del_settings_from_config(const wxDataViewItem& parent_item);
|
||||
|
|
|
|||
|
|
@ -2911,7 +2911,7 @@ ObjectTableDialog::ObjectTableDialog(wxWindow* parent, Plater* platerObj, Model
|
|||
wxSize panel_size = m_obj_panel->get_init_size();
|
||||
g_max_size_from_parent = maxSize;
|
||||
if ((maxSize.GetWidth() == -1) || (maxSize.GetHeight() == -1)) {
|
||||
wxDisplay display(wxDisplay::GetFromWindow(this));
|
||||
wxDisplay display(this);
|
||||
//auto drect = display.GetGeometry();
|
||||
wxRect client_area = display.GetClientArea ();
|
||||
g_max_size_from_parent.SetWidth(client_area.GetWidth());
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
||||
#include "slic3r/GUI/format.hpp"
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/Utils/UndoRedo.hpp"
|
||||
|
||||
|
||||
|
|
@ -181,12 +182,6 @@ void GLGizmoFdmSupports::on_set_state()
|
|||
}
|
||||
}
|
||||
|
||||
static std::string into_u8(const wxString& str)
|
||||
{
|
||||
auto buffer_utf8 = str.utf8_str();
|
||||
return std::string(buffer_utf8.data());
|
||||
}
|
||||
|
||||
void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_limit)
|
||||
{
|
||||
init_print_instance();
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
#include "slic3r/GUI/format.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
||||
#include "slic3r/GUI/NotificationManager.hpp"
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "slic3r/Utils/UndoRedo.hpp"
|
||||
|
|
@ -291,12 +292,6 @@ static void render_extruders_combo(const std::string &labe
|
|||
selection_idx = selection_out;
|
||||
}
|
||||
|
||||
static std::string into_u8(const wxString& str)
|
||||
{
|
||||
auto buffer_utf8 = str.utf8_str();
|
||||
return std::string(buffer_utf8.data());
|
||||
}
|
||||
|
||||
void GLGizmoMmuSegmentation::show_tooltip_information(float caption_max, float x, float y)
|
||||
{
|
||||
ImTextureID normal_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP);
|
||||
|
|
|
|||
|
|
@ -1064,7 +1064,10 @@ void TriangleSelectorPatch::render(ImGuiWrapper* imgui)
|
|||
size_t color_idx = (size_t)patch.type;
|
||||
color = m_ebt_colors[color_idx];
|
||||
}
|
||||
shader->set_uniform("uniform_color", color);
|
||||
//to make black not too hard too see
|
||||
std::array<float, 4> new_color = adjust_color_for_rendering(color);
|
||||
shader->set_uniform("uniform_color", new_color);
|
||||
//shader->set_uniform("uniform_color", color);
|
||||
this->render(buffer_idx);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include "slic3r/GUI/ImGuiWrapper.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/Utils/UndoRedo.hpp"
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
|
@ -155,12 +156,6 @@ void GLGizmoSeam::tool_changed(wchar_t old_tool, wchar_t new_tool)
|
|||
}
|
||||
}
|
||||
|
||||
static std::string into_u8(const wxString& str)
|
||||
{
|
||||
auto buffer_utf8 = str.utf8_str();
|
||||
return std::string(buffer_utf8.data());
|
||||
}
|
||||
|
||||
void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
|
||||
{
|
||||
if (! m_c->selection_info()->model_object())
|
||||
|
|
|
|||
281
src/slic3r/GUI/HMS.cpp
Normal file
281
src/slic3r/GUI/HMS.cpp
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
#include "HMS.hpp"
|
||||
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
int get_hms_info_version(std::string& version)
|
||||
{
|
||||
AppConfig* config = wxGetApp().app_config;
|
||||
if (!config)
|
||||
return -1;
|
||||
std::string hms_host = config->get_hms_host();
|
||||
if(hms_host.empty()) {
|
||||
BOOST_LOG_TRIVIAL(error) << "hms_host is empty";
|
||||
return -1;
|
||||
}
|
||||
int result = -1;
|
||||
version = "";
|
||||
std::string url = (boost::format("https://%1%/GetVersion.php") % hms_host).str();
|
||||
Slic3r::Http http = Slic3r::Http::get(url);
|
||||
http.timeout_max(10)
|
||||
.on_complete([&result, &version](std::string body, unsigned status){
|
||||
try {
|
||||
json j = json::parse(body);
|
||||
if (j.contains("ver")) {
|
||||
version = std::to_string(j["ver"].get<long long>());
|
||||
}
|
||||
} catch (...) {
|
||||
;
|
||||
}
|
||||
})
|
||||
.on_error([&result](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << "get_hms_info_version: body = " << body << ", status = " << status << ", error = " << error;
|
||||
result = -1;
|
||||
})
|
||||
.perform_sync();
|
||||
return result;
|
||||
}
|
||||
|
||||
int HMSQuery::download_hms_info()
|
||||
{
|
||||
AppConfig* config = wxGetApp().app_config;
|
||||
if (!config) return -1;
|
||||
|
||||
std::string hms_host = wxGetApp().app_config->get_hms_host();
|
||||
std::string lang_code = wxGetApp().app_config->get_language_code();
|
||||
std::string url = (boost::format("https://%1%/query.php?lang=%2%") % hms_host % lang_code).str();
|
||||
|
||||
Slic3r::Http http = Slic3r::Http::get(url);
|
||||
|
||||
http.on_complete([this](std::string body, unsigned status) {
|
||||
try {
|
||||
json j = json::parse(body);
|
||||
if (j.contains("result")) {
|
||||
if (j["result"] == 0 && j.contains("data")) {
|
||||
this->m_hms_json = j["data"];
|
||||
if (j.contains("ver"))
|
||||
m_hms_json["version"] = std::to_string(j["ver"].get<long long>());
|
||||
} else {
|
||||
this->m_hms_json.clear();
|
||||
BOOST_LOG_TRIVIAL(info) << "HMSQuery: update hms info error = " << j["result"].get<int>();
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
;
|
||||
}
|
||||
})
|
||||
.timeout_max(20)
|
||||
.on_error([](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << "HMSQuery: update hms info error = " << error << ", body = " << body << ", status = " << status;
|
||||
}).perform_sync();
|
||||
|
||||
save_to_local();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HMSQuery::load_from_local(std::string &version_info)
|
||||
{
|
||||
if (data_dir().empty()) {
|
||||
version_info = "";
|
||||
BOOST_LOG_TRIVIAL(error) << "HMS: load_from_local, data_dir() is empty";
|
||||
return -1;
|
||||
}
|
||||
std::string filename = get_hms_file();
|
||||
std::string dir_str = (boost::filesystem::path(data_dir()) / filename).make_preferred().string();
|
||||
std::ifstream json_file(encode_path(dir_str.c_str()));
|
||||
try {
|
||||
if (json_file.is_open()) {
|
||||
json_file >> m_hms_json;
|
||||
if (m_hms_json.contains("version")) {
|
||||
version_info = m_hms_json["version"].get<std::string>();
|
||||
return 0;
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(warning) << "HMS: load_from_local, no version info";
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
} catch(...) {
|
||||
version_info = "";
|
||||
return -1;
|
||||
}
|
||||
version_info = "";
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HMSQuery::save_to_local()
|
||||
{
|
||||
if (data_dir().empty()) {
|
||||
BOOST_LOG_TRIVIAL(error) << "HMS: save_to_local, data_dir() is empty";
|
||||
return -1;
|
||||
}
|
||||
std::string filename = get_hms_file();
|
||||
std::string dir_str = (boost::filesystem::path(data_dir()) / filename).make_preferred().string();
|
||||
std::ofstream json_file(encode_path(dir_str.c_str()));
|
||||
if (json_file.is_open()) {
|
||||
json_file << std::setw(4) << m_hms_json << std::endl;
|
||||
json_file.close();
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string HMSQuery::get_hms_file()
|
||||
{
|
||||
AppConfig* config = wxGetApp().app_config;
|
||||
if (!config)
|
||||
return HMS_INFO_FILE;
|
||||
std::string lang_code = wxGetApp().app_config->get_language_code();
|
||||
return (boost::format("hms_%1%.json") % lang_code).str();
|
||||
}
|
||||
|
||||
wxString HMSQuery::query_hms_msg(std::string long_error_code)
|
||||
{
|
||||
if (long_error_code.empty())
|
||||
return wxEmptyString;
|
||||
AppConfig* config = wxGetApp().app_config;
|
||||
if (!config) return wxEmptyString;
|
||||
|
||||
std::string hms_host = wxGetApp().app_config->get_hms_host();
|
||||
std::string lang_code = wxGetApp().app_config->get_language_code();
|
||||
|
||||
if (m_hms_json.contains("device_hms")) {
|
||||
if (m_hms_json["device_hms"].contains(lang_code)) {
|
||||
for (auto item = m_hms_json["device_hms"][lang_code].begin(); item != m_hms_json["device_hms"][lang_code].end(); item++) {
|
||||
if (item->contains("ecode") && (*item)["ecode"].get<std::string>() == long_error_code) {
|
||||
if (item->contains("intro")) {
|
||||
return wxString::FromUTF8((*item)["intro"].get<std::string>());
|
||||
}
|
||||
}
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "hms: query_hms_msg, not found error_code = " << long_error_code;
|
||||
}
|
||||
} else {
|
||||
return wxEmptyString;
|
||||
}
|
||||
return wxEmptyString;
|
||||
}
|
||||
|
||||
wxString HMSQuery::query_error_msg(std::string error_code)
|
||||
{
|
||||
AppConfig* config = wxGetApp().app_config;
|
||||
if (!config) return wxEmptyString;
|
||||
|
||||
std::string hms_host = wxGetApp().app_config->get_hms_host();
|
||||
std::string lang_code = wxGetApp().app_config->get_language_code();
|
||||
|
||||
if (m_hms_json.contains("device_error")) {
|
||||
if (m_hms_json["device_error"].contains(lang_code)) {
|
||||
for (auto item = m_hms_json["device_error"][lang_code].begin(); item != m_hms_json["device_error"][lang_code].end(); item++) {
|
||||
if (item->contains("ecode") && (*item)["ecode"].get<std::string>() == error_code) {
|
||||
if (item->contains("intro")) {
|
||||
return wxString::FromUTF8((*item)["intro"].get<std::string>());
|
||||
}
|
||||
}
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "hms: query_error_msg, not found error_code = " << error_code;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return wxEmptyString;
|
||||
}
|
||||
return wxEmptyString;
|
||||
}
|
||||
|
||||
wxString HMSQuery::query_print_error_msg(int print_error)
|
||||
{
|
||||
char buf[32];
|
||||
::sprintf(buf, "%08X", print_error);
|
||||
return query_error_msg(std::string(buf));
|
||||
}
|
||||
|
||||
int HMSQuery::check_hms_info()
|
||||
{
|
||||
int result = 0;
|
||||
bool download_new_hms_info = true;
|
||||
|
||||
// load local hms json file
|
||||
std::string version = "";
|
||||
if (load_from_local(version) == 0) {
|
||||
BOOST_LOG_TRIVIAL(info) << "HMS: check_hms_info current version = " << version;
|
||||
std::string new_version;
|
||||
get_hms_info_version(new_version);
|
||||
BOOST_LOG_TRIVIAL(info) << "HMS: check_hms_info latest version = " << new_version;
|
||||
if (!version.empty() && version == new_version) {
|
||||
download_new_hms_info = false;
|
||||
}
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "HMS: check_hms_info need download new hms info = " << download_new_hms_info;
|
||||
// download if version is update
|
||||
if (download_new_hms_info) {
|
||||
result = download_hms_info();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string get_hms_wiki_url(std::string error_code)
|
||||
{
|
||||
AppConfig* config = wxGetApp().app_config;
|
||||
if (!config) return "";
|
||||
|
||||
std::string hms_host = wxGetApp().app_config->get_hms_host();
|
||||
std::string lang_code = wxGetApp().app_config->get_language_code();
|
||||
std::string url = (boost::format("https://%1%/index.php?e=%2%&s=device_hms&lang=%3%")
|
||||
% hms_host
|
||||
% error_code
|
||||
% lang_code).str();
|
||||
return url;
|
||||
}
|
||||
|
||||
std::string get_error_message(int error_code)
|
||||
{
|
||||
char buf[64];
|
||||
std::string result_str = "";
|
||||
std::sprintf(buf,"%08X",error_code);
|
||||
std::string hms_host = wxGetApp().app_config->get_hms_host();
|
||||
std::string get_lang = wxGetApp().app_config->get_language_code();
|
||||
|
||||
std::string url = (boost::format("https://%1%/query.php?lang=%2%&e=%3%")
|
||||
%hms_host
|
||||
%get_lang
|
||||
%buf).str();
|
||||
|
||||
Slic3r::Http http = Slic3r::Http::get(url);
|
||||
http.header("accept", "application/json")
|
||||
.timeout_max(10)
|
||||
.on_complete([get_lang, &result_str](std::string body, unsigned status) {
|
||||
try {
|
||||
json j = json::parse(body);
|
||||
if (j.contains("result")) {
|
||||
if (j["result"].get<int>() == 0) {
|
||||
if (j.contains("data")) {
|
||||
json jj = j["data"];
|
||||
if (jj.contains("device_error")) {
|
||||
if (jj["device_error"].contains(get_lang)) {
|
||||
if (jj["device_error"][get_lang].size() > 0) {
|
||||
if (!jj["device_error"][get_lang][0]["intro"].empty() || !jj["device_error"][get_lang][0]["ecode"].empty()) {
|
||||
std::string error_info = jj["device_error"][get_lang][0]["intro"].get<std::string>();
|
||||
std::string error_code = jj["device_error"][get_lang][0]["ecode"].get<std::string>();
|
||||
error_code.insert(4, " ");
|
||||
result_str = from_u8(error_info).ToStdString() + "[" + error_code + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
;
|
||||
}
|
||||
})
|
||||
.on_error([](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(trace) << boost::format("[BBL ErrorMessage]: status=%1%, error=%2%, body=%3%") % status % error % body;
|
||||
}).perform_sync();
|
||||
|
||||
return result_str;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue