mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-20 05:07:51 -06:00
Merge branch 'master-remote' into feature/merge_remote_1.3
# Conflicts: # bbl/i18n/zh_cn/BambuStudio_zh_CN.po # resources/i18n/zh_cn/BambuStudio.mo # resources/profiles/Voron.json # resources/profiles/Voron/filament/Voron Generic ABS.json # resources/profiles/Voron/filament/Voron Generic ASA.json # resources/profiles/Voron/filament/Voron Generic PA-CF.json # resources/profiles/Voron/filament/Voron Generic PA.json # resources/profiles/Voron/filament/Voron Generic PC.json # resources/profiles/Voron/filament/Voron Generic PETG.json # resources/profiles/Voron/filament/Voron Generic PLA-CF.json # resources/profiles/Voron/filament/Voron Generic PLA.json # resources/profiles/Voron/filament/Voron Generic PVA.json # resources/profiles/Voron/filament/Voron Generic TPU.json # resources/profiles/Voron/filament/fdm_filament_abs.json # resources/profiles/Voron/filament/fdm_filament_asa.json # resources/profiles/Voron/filament/fdm_filament_common.json # resources/profiles/Voron/filament/fdm_filament_pa.json # resources/profiles/Voron/filament/fdm_filament_pc.json # resources/profiles/Voron/filament/fdm_filament_pet.json # resources/profiles/Voron/filament/fdm_filament_pla.json # resources/profiles/Voron/filament/fdm_filament_pva.json # resources/profiles/Voron/filament/fdm_filament_tpu.json # resources/profiles/Voron/machine/Voron 0.1 0.4 nozzle.json # resources/profiles/Voron/machine/Voron 0.1.json # resources/profiles/Voron/machine/Voron 2.4 250 0.4 nozzle.json # resources/profiles/Voron/machine/Voron 2.4 250.json # resources/profiles/Voron/machine/Voron 2.4 300 0.4 nozzle.json # resources/profiles/Voron/machine/Voron 2.4 300.json # resources/profiles/Voron/machine/Voron 2.4 350 0.4 nozzle.json # resources/profiles/Voron/machine/Voron 2.4 350.json # resources/profiles/Voron/machine/Voron Trident 250 0.4 nozzle.json # resources/profiles/Voron/machine/Voron Trident 250.json # resources/profiles/Voron/machine/Voron Trident 300 0.4 nozzle.json # resources/profiles/Voron/machine/Voron Trident 300.json # resources/profiles/Voron/machine/Voron Trident 350 0.4 nozzle.json # resources/profiles/Voron/machine/Voron Trident 350.json # resources/profiles/Voron/machine/fdm_klipper_common.json # resources/profiles/Voron/process/0.08mm Extra Fine @Voron.json # resources/profiles/Voron/process/0.12mm Fine @Voron.json # resources/profiles/Voron/process/0.15mm Optimal @Voron.json # resources/profiles/Voron/process/0.20mm Standard @Voron.json # resources/profiles/Voron/process/0.24mm Draft @Voron.json # resources/profiles/Voron/process/0.28mm Extra Draft @Voron.json # resources/profiles/Voron/process/fdm_process_voron_common.json # src/libslic3r/Preset.cpp # src/libslic3r/PrintConfig.cpp # src/libslic3r/PrintConfig.hpp # src/libslic3r/PrintObject.cpp # src/slic3r/GUI/BackgroundSlicingProcess.cpp # src/slic3r/GUI/Field.cpp # src/slic3r/GUI/GLToolbar.cpp # src/slic3r/GUI/GLToolbar.hpp # src/slic3r/GUI/MainFrame.cpp # src/slic3r/GUI/MainFrame.hpp # src/slic3r/GUI/NotificationManager.cpp # src/slic3r/GUI/PhysicalPrinterDialog.cpp # src/slic3r/GUI/PhysicalPrinterDialog.hpp # src/slic3r/GUI/Plater.cpp # src/slic3r/GUI/Plater.hpp # src/slic3r/GUI/PrintHostDialogs.cpp # src/slic3r/GUI/PrintHostDialogs.hpp # src/slic3r/Utils/PrintHost.cpp
This commit is contained in:
commit
35455e6533
278 changed files with 14888 additions and 2414 deletions
|
@ -170,10 +170,10 @@ void AppConfig::set_defaults()
|
|||
set_bool("reverse_mouse_wheel_zoom", false);
|
||||
#endif
|
||||
|
||||
#ifdef SUPPORT_SHOW_HINTS
|
||||
//#ifdef SUPPORT_SHOW_HINTS
|
||||
if (get("show_hints").empty())
|
||||
set_bool("show_hints", true);
|
||||
#endif
|
||||
//#endif
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
|
@ -273,6 +273,10 @@ void AppConfig::set_defaults()
|
|||
set("precise_control", "none/mouse left");
|
||||
}
|
||||
|
||||
if (get("download_path").empty()) {
|
||||
set("download_path", "");
|
||||
}
|
||||
|
||||
if (get("mouse_wheel").empty()) {
|
||||
set("mouse_wheel", "0");
|
||||
}
|
||||
|
@ -284,7 +288,7 @@ void AppConfig::set_defaults()
|
|||
if (get("backup_interval").empty()) {
|
||||
set("backup_interval", "10");
|
||||
}
|
||||
|
||||
|
||||
if (get("curr_bed_type").empty()) {
|
||||
set("curr_bed_type", "0");
|
||||
}
|
||||
|
@ -299,10 +303,6 @@ void AppConfig::set_defaults()
|
|||
// }
|
||||
// #endif
|
||||
|
||||
if (get("uniform_scale").empty()) {
|
||||
set("uniform_scale", "1");
|
||||
}
|
||||
|
||||
// Remove legacy window positions/sizes
|
||||
erase("app", "main_frame_maximized");
|
||||
erase("app", "main_frame_pos");
|
||||
|
|
|
@ -21,7 +21,7 @@ DistributedBeadingStrategy::DistributedBeadingStrategy(const coord_t optimal_wid
|
|||
name = "DistributedBeadingStrategy";
|
||||
}
|
||||
|
||||
DistributedBeadingStrategy::Beading DistributedBeadingStrategy::compute(coord_t thickness, coord_t bead_count) const
|
||||
DistributedBeadingStrategy::Beading DistributedBeadingStrategy::compute(const coord_t thickness, const coord_t bead_count) const
|
||||
{
|
||||
Beading ret;
|
||||
|
||||
|
@ -40,18 +40,24 @@ DistributedBeadingStrategy::Beading DistributedBeadingStrategy::compute(coord_t
|
|||
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);
|
||||
const float total_weight = std::accumulate(weights.cbegin(), weights.cend(), 0.f);
|
||||
coord_t accumulated_width = 0;
|
||||
for (coord_t bead_idx = 0; bead_idx < bead_count; bead_idx++) {
|
||||
const float weight_fraction = weights[bead_idx] / total_weight;
|
||||
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;
|
||||
const coord_t width = (bead_idx == bead_count - 1) ? thickness - accumulated_width : optimal_width + splitup_left_over_weight;
|
||||
|
||||
// Be aware that toolpath_locations is computed by dividing the width by 2, so toolpath_locations
|
||||
// could be off by 1 because of rounding errors.
|
||||
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);
|
||||
accumulated_width += width;
|
||||
}
|
||||
ret.left_over = 0;
|
||||
assert((accumulated_width + ret.left_over) == thickness);
|
||||
} else if (bead_count == 2) {
|
||||
const coord_t outer_width = thickness / 2;
|
||||
ret.bead_widths.emplace_back(outer_width);
|
||||
|
@ -68,6 +74,13 @@ DistributedBeadingStrategy::Beading DistributedBeadingStrategy::compute(coord_t
|
|||
ret.left_over = thickness;
|
||||
}
|
||||
|
||||
assert(([&ret = std::as_const(ret), thickness]() -> bool {
|
||||
coord_t total_bead_width = 0;
|
||||
for (const coord_t &bead_width : ret.bead_widths)
|
||||
total_bead_width += bead_width;
|
||||
return (total_bead_width + ret.left_over) == thickness;
|
||||
}()));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include "Utils.hpp"
|
||||
#include "SVG.hpp"
|
||||
#include "Geometry/VoronoiVisualUtils.hpp"
|
||||
#include "Geometry/VoronoiUtilsCgal.hpp"
|
||||
#include "../EdgeGrid.hpp"
|
||||
|
||||
#define SKELETAL_TRAPEZOIDATION_BEAD_SEARCH_MAX 1000 //A limit to how long it'll keep searching for adjacent beads. Increasing will re-use beadings more often (saving performance), but search longer for beading (costing performance).
|
||||
|
@ -43,6 +44,71 @@ template<> struct segment_traits<Slic3r::Arachne::PolygonsSegmentIndex>
|
|||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
static void export_graph_to_svg(const std::string &path,
|
||||
SkeletalTrapezoidationGraph &graph,
|
||||
const Polygons &polys,
|
||||
const std::vector<std::shared_ptr<LineJunctions>> &edge_junctions = {},
|
||||
const bool beat_count = true,
|
||||
const bool transition_middles = true,
|
||||
const bool transition_ends = true)
|
||||
{
|
||||
const std::vector<std::string> colors = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "green", "yellow"};
|
||||
coordf_t stroke_width = scale_(0.03);
|
||||
BoundingBox bbox = get_extents(polys);
|
||||
for (const auto &node : graph.nodes)
|
||||
bbox.merge(node.p);
|
||||
|
||||
bbox.offset(scale_(1.));
|
||||
|
||||
::Slic3r::SVG svg(path.c_str(), bbox);
|
||||
for (const auto &line : to_lines(polys))
|
||||
svg.draw(line, "gray", stroke_width);
|
||||
|
||||
for (const auto &edge : graph.edges)
|
||||
svg.draw(Line(edge.from->p, edge.to->p), (edge.data.centralIsSet() && edge.data.isCentral()) ? "blue" : "cyan", stroke_width);
|
||||
|
||||
for (const auto &line_junction : edge_junctions)
|
||||
for (const auto &extrusion_junction : *line_junction)
|
||||
svg.draw(extrusion_junction.p, "orange", coord_t(stroke_width * 2.));
|
||||
|
||||
if (beat_count) {
|
||||
for (const auto &node : graph.nodes) {
|
||||
svg.draw(node.p, "red", coord_t(stroke_width * 1.6));
|
||||
svg.draw_text(node.p, std::to_string(node.data.bead_count).c_str(), "black", 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (transition_middles) {
|
||||
for (auto &edge : graph.edges) {
|
||||
if (std::shared_ptr<std::list<SkeletalTrapezoidationEdge::TransitionMiddle>> transitions = edge.data.getTransitions(); transitions) {
|
||||
for (auto &transition : *transitions) {
|
||||
Line edge_line = Line(edge.to->p, edge.from->p);
|
||||
double edge_length = edge_line.length();
|
||||
Point pt = edge_line.a + (edge_line.vector().cast<double>() * (double(transition.pos) / edge_length)).cast<coord_t>();
|
||||
svg.draw(pt, "magenta", coord_t(stroke_width * 1.5));
|
||||
svg.draw_text(pt, std::to_string(transition.lower_bead_count).c_str(), "black", 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (transition_ends) {
|
||||
for (auto &edge : graph.edges) {
|
||||
if (std::shared_ptr<std::list<SkeletalTrapezoidationEdge::TransitionEnd>> transitions = edge.data.getTransitionEnds(); transitions) {
|
||||
for (auto &transition : *transitions) {
|
||||
Line edge_line = Line(edge.to->p, edge.from->p);
|
||||
double edge_length = edge_line.length();
|
||||
Point pt = edge_line.a + (edge_line.vector().cast<double>() * (double(transition.pos) / edge_length)).cast<coord_t>();
|
||||
svg.draw(pt, transition.is_lower_end ? "green" : "lime", coord_t(stroke_width * 1.5));
|
||||
svg.draw_text(pt, std::to_string(transition.lower_bead_count).c_str(), "black", 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
SkeletalTrapezoidation::node_t& SkeletalTrapezoidation::makeNode(vd_t::vertex_type& vd_node, Point p)
|
||||
{
|
||||
auto he_node_it = vd_node_to_he_node.find(&vd_node);
|
||||
|
@ -285,7 +351,6 @@ std::vector<Point> SkeletalTrapezoidation::discretize(const vd_t::edge_type& vd_
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
bool SkeletalTrapezoidation::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)
|
||||
{
|
||||
if (cell.incident_edge()->is_infinite())
|
||||
|
@ -386,7 +451,23 @@ SkeletalTrapezoidation::SkeletalTrapezoidation(const Polygons& polys, const Bead
|
|||
constructFromPolygons(polys);
|
||||
}
|
||||
|
||||
bool detect_missing_voronoi_vertex(const Geometry::VoronoiDiagram &voronoi_diagram, const std::vector<SkeletalTrapezoidation::Segment> &segments) {
|
||||
static bool has_finite_edge_with_non_finite_vertex(const Geometry::VoronoiDiagram &voronoi_diagram)
|
||||
{
|
||||
for (const VoronoiUtils::vd_t::edge_type &edge : voronoi_diagram.edges()) {
|
||||
if (edge.is_finite()) {
|
||||
assert(edge.vertex0() != nullptr && edge.vertex1() != nullptr);
|
||||
if (edge.vertex0() == nullptr || edge.vertex1() == nullptr || !VoronoiUtils::is_finite(*edge.vertex0()) ||
|
||||
!VoronoiUtils::is_finite(*edge.vertex1()))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool detect_missing_voronoi_vertex(const Geometry::VoronoiDiagram &voronoi_diagram, const std::vector<SkeletalTrapezoidation::Segment> &segments) {
|
||||
if (has_finite_edge_with_non_finite_vertex(voronoi_diagram))
|
||||
return true;
|
||||
|
||||
for (VoronoiUtils::vd_t::cell_type cell : voronoi_diagram.cells()) {
|
||||
if (!cell.incident_edge())
|
||||
continue; // There is no spoon
|
||||
|
@ -405,7 +486,8 @@ bool detect_missing_voronoi_vertex(const Geometry::VoronoiDiagram &voronoi_diagr
|
|||
VoronoiUtils::vd_t::edge_type *ending_vd_edge = nullptr;
|
||||
VoronoiUtils::vd_t::edge_type *edge = cell.incident_edge();
|
||||
do {
|
||||
if (edge->is_infinite()) continue;
|
||||
if (edge->is_infinite() || edge->vertex0() == nullptr || edge->vertex1() == nullptr || !VoronoiUtils::is_finite(*edge->vertex0()) || !VoronoiUtils::is_finite(*edge->vertex1()))
|
||||
continue;
|
||||
|
||||
Vec2i64 v0 = VoronoiUtils::p(edge->vertex0());
|
||||
Vec2i64 v1 = VoronoiUtils::p(edge->vertex1());
|
||||
|
@ -432,8 +514,71 @@ bool detect_missing_voronoi_vertex(const Geometry::VoronoiDiagram &voronoi_diagr
|
|||
return false;
|
||||
}
|
||||
|
||||
static bool has_missing_twin_edge(const SkeletalTrapezoidationGraph &graph)
|
||||
{
|
||||
for (const auto &edge : graph.edges)
|
||||
if (edge.twin == nullptr)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
inline static std::unordered_map<Point, Point, PointHash> try_to_fix_degenerated_voronoi_diagram_by_rotation(
|
||||
Geometry::VoronoiDiagram &voronoi_diagram,
|
||||
const Polygons &polys,
|
||||
Polygons &polys_rotated,
|
||||
std::vector<SkeletalTrapezoidation::Segment> &segments,
|
||||
const double fix_angle)
|
||||
{
|
||||
std::unordered_map<Point, Point, PointHash> vertex_mapping;
|
||||
for (Polygon &poly : polys_rotated)
|
||||
poly.rotate(fix_angle);
|
||||
|
||||
assert(polys_rotated.size() == polys.size());
|
||||
for (size_t poly_idx = 0; poly_idx < polys.size(); ++poly_idx) {
|
||||
assert(polys_rotated[poly_idx].size() == polys[poly_idx].size());
|
||||
for (size_t point_idx = 0; point_idx < polys[poly_idx].size(); ++point_idx)
|
||||
vertex_mapping.insert({polys_rotated[poly_idx][point_idx], polys[poly_idx][point_idx]});
|
||||
}
|
||||
|
||||
segments.clear();
|
||||
for (size_t poly_idx = 0; poly_idx < polys_rotated.size(); poly_idx++)
|
||||
for (size_t point_idx = 0; point_idx < polys_rotated[poly_idx].size(); point_idx++)
|
||||
segments.emplace_back(&polys_rotated, poly_idx, point_idx);
|
||||
|
||||
voronoi_diagram.clear();
|
||||
construct_voronoi(segments.begin(), segments.end(), &voronoi_diagram);
|
||||
|
||||
#ifdef ARACHNE_DEBUG_VORONOI
|
||||
{
|
||||
static int iRun = 0;
|
||||
dump_voronoi_to_svg(debug_out_path("arachne_voronoi-diagram-rotated-%d.svg", iRun++).c_str(), voronoi_diagram, to_points(polys), to_lines(polys));
|
||||
}
|
||||
#endif
|
||||
|
||||
assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram));
|
||||
|
||||
return vertex_mapping;
|
||||
}
|
||||
|
||||
inline static void rotate_back_skeletal_trapezoidation_graph_after_fix(SkeletalTrapezoidationGraph &graph,
|
||||
const double fix_angle,
|
||||
const std::unordered_map<Point, Point, PointHash> &vertex_mapping)
|
||||
{
|
||||
for (STHalfEdgeNode &node : graph.nodes) {
|
||||
// If a mapping exists between a rotated point and an original point, use this mapping. Otherwise, rotate a point in the opposite direction.
|
||||
if (auto node_it = vertex_mapping.find(node.p); node_it != vertex_mapping.end())
|
||||
node.p = node_it->second;
|
||||
else
|
||||
node.p.rotate(-fix_angle);
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
|
||||
{
|
||||
#ifdef ARACHNE_DEBUG
|
||||
this->outline = polys;
|
||||
#endif
|
||||
|
||||
// Check self intersections.
|
||||
assert([&polys]() -> bool {
|
||||
EdgeGrid::Grid grid;
|
||||
|
@ -450,39 +595,57 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
|
|||
for (size_t point_idx = 0; point_idx < polys[poly_idx].size(); point_idx++)
|
||||
segments.emplace_back(&polys, poly_idx, point_idx);
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
{
|
||||
static int iRun = 0;
|
||||
BoundingBox bbox = get_extents(polys);
|
||||
SVG svg(debug_out_path("arachne_voronoi-input-%d.svg", iRun++).c_str(), bbox);
|
||||
svg.draw_outline(polys, "black", scaled<coordf_t>(0.03f));
|
||||
}
|
||||
#endif
|
||||
|
||||
Geometry::VoronoiDiagram voronoi_diagram;
|
||||
construct_voronoi(segments.begin(), segments.end(), &voronoi_diagram);
|
||||
|
||||
// Try to detect cases when some Voronoi vertex is missing.
|
||||
// When any Voronoi vertex is missing, rotate input polygon and try again.
|
||||
const bool has_missing_voronoi_vertex = detect_missing_voronoi_vertex(voronoi_diagram, segments);
|
||||
const double fix_angle = PI / 6;
|
||||
#ifdef ARACHNE_DEBUG_VORONOI
|
||||
{
|
||||
static int iRun = 0;
|
||||
dump_voronoi_to_svg(debug_out_path("arachne_voronoi-diagram-%d.svg", iRun++).c_str(), voronoi_diagram, to_points(polys), to_lines(polys));
|
||||
}
|
||||
#endif
|
||||
|
||||
// Try to detect cases when some Voronoi vertex is missing and when
|
||||
// the Voronoi diagram is not planar.
|
||||
// When any Voronoi vertex is missing, or the Voronoi diagram is not
|
||||
// planar, rotate the input polygon and try again.
|
||||
const bool has_missing_voronoi_vertex = detect_missing_voronoi_vertex(voronoi_diagram, segments);
|
||||
// Detection of non-planar Voronoi diagram detects at least GH issues #8474, #8514 and #8446.
|
||||
const bool is_voronoi_diagram_planar = Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram);
|
||||
const double fix_angle = PI / 6;
|
||||
|
||||
std::unordered_map<Point, Point, PointHash> vertex_mapping;
|
||||
// polys_copy is referenced through items stored in the std::vector segments.
|
||||
Polygons polys_copy = polys;
|
||||
if (has_missing_voronoi_vertex) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "Detected missing Voronoi vertex, input polygons will be rotated back and forth.";
|
||||
for (Polygon &poly : polys_copy)
|
||||
poly.rotate(fix_angle);
|
||||
if (has_missing_voronoi_vertex || !is_voronoi_diagram_planar) {
|
||||
if (has_missing_voronoi_vertex)
|
||||
BOOST_LOG_TRIVIAL(warning) << "Detected missing Voronoi vertex, input polygons will be rotated back and forth.";
|
||||
else if (!is_voronoi_diagram_planar)
|
||||
BOOST_LOG_TRIVIAL(warning) << "Detected non-planar Voronoi diagram, input polygons will be rotated back and forth.";
|
||||
|
||||
assert(polys_copy.size() == polys.size());
|
||||
for (size_t poly_idx = 0; poly_idx < polys.size(); ++poly_idx) {
|
||||
assert(polys_copy[poly_idx].size() == polys[poly_idx].size());
|
||||
for (size_t point_idx = 0; point_idx < polys[poly_idx].size(); ++point_idx)
|
||||
vertex_mapping.insert({polys[poly_idx][point_idx], polys_copy[poly_idx][point_idx]});
|
||||
}
|
||||
vertex_mapping = try_to_fix_degenerated_voronoi_diagram_by_rotation(voronoi_diagram, polys, polys_copy, segments, fix_angle);
|
||||
|
||||
segments.clear();
|
||||
for (size_t poly_idx = 0; poly_idx < polys_copy.size(); poly_idx++)
|
||||
for (size_t point_idx = 0; point_idx < polys_copy[poly_idx].size(); point_idx++)
|
||||
segments.emplace_back(&polys_copy, poly_idx, point_idx);
|
||||
|
||||
voronoi_diagram.clear();
|
||||
construct_voronoi(segments.begin(), segments.end(), &voronoi_diagram);
|
||||
assert(!detect_missing_voronoi_vertex(voronoi_diagram, segments));
|
||||
assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram));
|
||||
if (detect_missing_voronoi_vertex(voronoi_diagram, segments))
|
||||
BOOST_LOG_TRIVIAL(error) << "Detected missing Voronoi vertex even after the rotation of input.";
|
||||
else if (!Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram))
|
||||
BOOST_LOG_TRIVIAL(error) << "Detected non-planar Voronoi diagram even after the rotation of input.";
|
||||
}
|
||||
|
||||
bool degenerated_voronoi_diagram = has_missing_voronoi_vertex || !is_voronoi_diagram_planar;
|
||||
|
||||
process_voronoi_diagram:
|
||||
assert(this->graph.edges.empty() && this->graph.nodes.empty() && this->vd_edge_to_he_edge.empty() && this->vd_node_to_he_node.empty());
|
||||
for (vd_t::cell_type cell : voronoi_diagram.cells()) {
|
||||
if (!cell.incident_edge())
|
||||
continue; // There is no spoon
|
||||
|
@ -538,16 +701,43 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
|
|||
prev_edge->to->data.distance_to_boundary = 0;
|
||||
}
|
||||
|
||||
if (has_missing_voronoi_vertex) {
|
||||
for (node_t &node : graph.nodes) {
|
||||
// If a mapping exists between a rotated point and an original point, use this mapping. Otherwise, rotate a point in the opposite direction.
|
||||
if (auto node_it = vertex_mapping.find(node.p); node_it != vertex_mapping.end())
|
||||
node.p = node_it->second;
|
||||
else
|
||||
node.p.rotate(-fix_angle);
|
||||
}
|
||||
// For some input polygons, as in GH issues #8474 and #8514 resulting Voronoi diagram is degenerated because it is not planar.
|
||||
// When this degenerated Voronoi diagram is processed, the resulting half-edge structure contains some edges that don't have
|
||||
// a twin edge. Based on this, we created a fast mechanism that detects those causes and tries to recompute the Voronoi
|
||||
// diagram on slightly rotated input polygons that usually make the Voronoi generator generate a non-degenerated Voronoi diagram.
|
||||
if (!degenerated_voronoi_diagram && has_missing_twin_edge(this->graph)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Detected degenerated Voronoi diagram, input polygons will be rotated back and forth.";
|
||||
degenerated_voronoi_diagram = true;
|
||||
vertex_mapping = try_to_fix_degenerated_voronoi_diagram_by_rotation(voronoi_diagram, polys, polys_copy, segments, fix_angle);
|
||||
|
||||
assert(!detect_missing_voronoi_vertex(voronoi_diagram, segments));
|
||||
if (detect_missing_voronoi_vertex(voronoi_diagram, segments))
|
||||
BOOST_LOG_TRIVIAL(error) << "Detected missing Voronoi vertex after the rotation of input.";
|
||||
|
||||
assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram));
|
||||
|
||||
this->graph.edges.clear();
|
||||
this->graph.nodes.clear();
|
||||
this->vd_edge_to_he_edge.clear();
|
||||
this->vd_node_to_he_node.clear();
|
||||
|
||||
goto process_voronoi_diagram;
|
||||
}
|
||||
|
||||
if (degenerated_voronoi_diagram) {
|
||||
assert(!has_missing_twin_edge(this->graph));
|
||||
|
||||
if (has_missing_twin_edge(this->graph))
|
||||
BOOST_LOG_TRIVIAL(error) << "Detected degenerated Voronoi diagram even after the rotation of input.";
|
||||
}
|
||||
|
||||
if (degenerated_voronoi_diagram)
|
||||
rotate_back_skeletal_trapezoidation_graph_after_fix(this->graph, fix_angle, vertex_mapping);
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram));
|
||||
#endif
|
||||
|
||||
separatePointyQuadEndNodes();
|
||||
|
||||
graph.collapseSmallEdges();
|
||||
|
@ -594,45 +784,62 @@ void SkeletalTrapezoidation::separatePointyQuadEndNodes()
|
|||
// vvvvvvvvvvvvvvvvvvvvv
|
||||
//
|
||||
|
||||
#if 0
|
||||
static void export_graph_to_svg(const std::string &path, const SkeletalTrapezoidationGraph &graph, const Polygons &polys)
|
||||
{
|
||||
const std::vector<std::string> colors = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "green", "yellow"};
|
||||
coordf_t stroke_width = scale_(0.05);
|
||||
BoundingBox bbox;
|
||||
for (const auto &node : graph.nodes)
|
||||
bbox.merge(node.p);
|
||||
|
||||
bbox.offset(scale_(1.));
|
||||
::Slic3r::SVG svg(path.c_str(), bbox);
|
||||
for (const auto &line : to_lines(polys))
|
||||
svg.draw(line, "red", stroke_width);
|
||||
|
||||
for (const auto &edge : graph.edges)
|
||||
svg.draw(Line(edge.from->p, edge.to->p), "cyan", scale_(0.01));
|
||||
}
|
||||
#endif
|
||||
|
||||
void SkeletalTrapezoidation::generateToolpaths(std::vector<VariableWidthLines> &generated_toolpaths, bool filter_outermost_central_edges)
|
||||
{
|
||||
#ifdef ARACHNE_DEBUG
|
||||
static int iRun = 0;
|
||||
#endif
|
||||
|
||||
p_generated_toolpaths = &generated_toolpaths;
|
||||
|
||||
updateIsCentral();
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
export_graph_to_svg(debug_out_path("ST-updateIsCentral-final-%d.svg", iRun), this->graph, this->outline);
|
||||
#endif
|
||||
|
||||
filterCentral(central_filter_dist);
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
export_graph_to_svg(debug_out_path("ST-filterCentral-final-%d.svg", iRun), this->graph, this->outline);
|
||||
#endif
|
||||
|
||||
if (filter_outermost_central_edges)
|
||||
filterOuterCentral();
|
||||
|
||||
updateBeadCount();
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
export_graph_to_svg(debug_out_path("ST-updateBeadCount-final-%d.svg", iRun), this->graph, this->outline);
|
||||
#endif
|
||||
|
||||
filterNoncentralRegions();
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
export_graph_to_svg(debug_out_path("ST-filterNoncentralRegions-final-%d.svg", iRun), this->graph, this->outline);
|
||||
#endif
|
||||
|
||||
generateTransitioningRibs();
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
export_graph_to_svg(debug_out_path("ST-generateTransitioningRibs-final-%d.svg", iRun), this->graph, this->outline);
|
||||
#endif
|
||||
|
||||
generateExtraRibs();
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
export_graph_to_svg(debug_out_path("ST-generateExtraRibs-final-%d.svg", iRun), this->graph, this->outline);
|
||||
#endif
|
||||
|
||||
generateSegments();
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
export_graph_to_svg(debug_out_path("ST-generateSegments-final-%d.svg", iRun), this->graph, this->outline);
|
||||
#endif
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
++iRun;
|
||||
#endif
|
||||
}
|
||||
|
||||
void SkeletalTrapezoidation::updateIsCentral()
|
||||
|
@ -844,11 +1051,24 @@ void SkeletalTrapezoidation::generateTransitioningRibs()
|
|||
|
||||
filterTransitionMids();
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
static int iRun = 0;
|
||||
export_graph_to_svg(debug_out_path("ST-generateTransitioningRibs-mids-%d.svg", iRun++), this->graph, this->outline);
|
||||
#endif
|
||||
|
||||
ptr_vector_t<std::list<TransitionEnd>> edge_transition_ends; // We only map the half edge in the upward direction. mapped items are not sorted
|
||||
generateAllTransitionEnds(edge_transition_ends);
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
export_graph_to_svg(debug_out_path("ST-generateTransitioningRibs-ends-%d.svg", iRun++), this->graph, this->outline);
|
||||
#endif
|
||||
|
||||
applyTransitions(edge_transition_ends);
|
||||
// Note that the shared pointer lists will be out of scope and thus destroyed here, since the remaining refs are weak_ptr.
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
++iRun;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
@ -1568,17 +1788,38 @@ void SkeletalTrapezoidation::generateSegments()
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
static int iRun = 0;
|
||||
export_graph_to_svg(debug_out_path("ST-generateSegments-before-propagation-%d.svg", iRun), this->graph, this->outline);
|
||||
#endif
|
||||
|
||||
propagateBeadingsUpward(upward_quad_mids, node_beadings);
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
export_graph_to_svg(debug_out_path("ST-generateSegments-upward-propagation-%d.svg", iRun), this->graph, this->outline);
|
||||
#endif
|
||||
|
||||
propagateBeadingsDownward(upward_quad_mids, node_beadings);
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
export_graph_to_svg(debug_out_path("ST-generateSegments-downward-propagation-%d.svg", iRun), this->graph, this->outline);
|
||||
#endif
|
||||
|
||||
ptr_vector_t<LineJunctions> edge_junctions; // junctions ordered high R to low R
|
||||
generateJunctions(node_beadings, edge_junctions);
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
export_graph_to_svg(debug_out_path("ST-generateSegments-junctions-%d.svg", iRun), this->graph, this->outline, edge_junctions);
|
||||
#endif
|
||||
|
||||
connectJunctions(edge_junctions);
|
||||
|
||||
|
||||
generateLocalMaximaSingleBeads();
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
++iRun;
|
||||
#endif
|
||||
}
|
||||
|
||||
SkeletalTrapezoidation::edge_t* SkeletalTrapezoidation::getQuadMaxRedgeTo(edge_t* quad_start_edge)
|
||||
|
@ -1811,7 +2052,10 @@ void SkeletalTrapezoidation::generateJunctions(ptr_vector_t<BeadingPropagation>&
|
|||
for (junction_idx = (std::max(size_t(1), beading->toolpath_locations.size()) - 1) / 2; junction_idx < num_junctions; junction_idx--)
|
||||
{
|
||||
coord_t bead_R = beading->toolpath_locations[junction_idx];
|
||||
if (bead_R <= start_R)
|
||||
// toolpath_locations computed inside DistributedBeadingStrategy could be off by 1 because of rounding errors.
|
||||
// In GH issue #8472, these roundings errors caused missing the middle extrusion.
|
||||
// Adding small epsilon should help resolve those cases.
|
||||
if (bead_R <= start_R + 1)
|
||||
{ // Junction coinciding with start node is used in this function call
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,10 @@
|
|||
#include "SkeletalTrapezoidationJoint.hpp"
|
||||
#include "libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp"
|
||||
#include "SkeletalTrapezoidationGraph.hpp"
|
||||
#include "../Geometry/Voronoi.hpp"
|
||||
|
||||
//#define ARACHNE_DEBUG
|
||||
//#define ARACHNE_DEBUG_VORONOI
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
@ -122,6 +126,10 @@ public:
|
|||
*/
|
||||
void generateToolpaths(std::vector<VariableWidthLines> &generated_toolpaths, bool filter_outermost_central_edges = false);
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
Polygons outline;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
/*!
|
||||
* Auxiliary for referencing one transition along an edge which may contain multiple transitions
|
||||
|
|
|
@ -24,18 +24,19 @@ 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)
|
||||
const size_t inset_count, const coord_t wall_0_inset, const coordf_t layer_height, 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)
|
||||
, layer_height(layer_height)
|
||||
, 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))
|
||||
, toolpaths_generated(false)
|
||||
, m_params(params)
|
||||
{
|
||||
}
|
||||
|
@ -312,60 +313,46 @@ void removeSmallAreas(Polygons &thiss, const double min_area_size, const bool re
|
|||
};
|
||||
|
||||
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--;
|
||||
if (remove_holes) {
|
||||
for (auto it = thiss.begin(); it < new_end;) {
|
||||
// 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
|
||||
continue; // Don't increment the iterator such that the polygon just swapped in is checked next.
|
||||
}
|
||||
++it;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} 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) {
|
||||
for (auto it = thiss.begin(); it < new_end;) {
|
||||
if (double area = ClipperLib::Area(to_path(*it)); 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
|
||||
continue;
|
||||
} else { // Don't self-swap the last Path
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
small_holes.push_back(*it);
|
||||
}
|
||||
}
|
||||
++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())) {
|
||||
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());
|
||||
}
|
||||
|
@ -471,7 +458,7 @@ const std::vector<VariableWidthLines> &WallToolPaths::generate()
|
|||
// 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).
|
||||
// didn't have twin edges. (a non-planar Voronoi diagram probably caused this).
|
||||
prepared_outline = union_(prepared_outline);
|
||||
|
||||
if (area(prepared_outline) <= 0) {
|
||||
|
@ -479,9 +466,14 @@ const std::vector<VariableWidthLines> &WallToolPaths::generate()
|
|||
return toolpaths;
|
||||
}
|
||||
|
||||
const float external_perimeter_extrusion_width = Flow::rounded_rectangle_extrusion_width_from_spacing(unscale<float>(bead_width_0), float(this->layer_height));
|
||||
const float perimeter_extrusion_width = Flow::rounded_rectangle_extrusion_width_from_spacing(unscale<float>(bead_width_x), float(this->layer_height));
|
||||
|
||||
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 double wall_split_middle_threshold = std::clamp(2. * unscaled<double>(this->min_bead_width) / external_perimeter_extrusion_width - 1., 0.01, 0.99); // For an uneven nr. of lines: When to split the middle wall into two.
|
||||
const double wall_add_middle_threshold = std::clamp(unscaled<double>(this->min_bead_width) / perimeter_extrusion_width, 0.01, 0.99); // 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
|
||||
|
@ -609,6 +601,14 @@ void WallToolPaths::stitchToolPaths(std::vector<VariableWidthLines> &toolpaths,
|
|||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// PolylineStitcher, in some cases, produced closed extrusion (polygons),
|
||||
// but the endpoints differ by a small distance. So we reconnect them.
|
||||
// FIXME Lukas H.: Investigate more deeply why it is happening.
|
||||
if (wall_polygon.junctions.front().p != wall_polygon.junctions.back().p &&
|
||||
(wall_polygon.junctions.back().p - wall_polygon.junctions.front().p).cast<double>().norm() < stitch_distance) {
|
||||
wall_polygon.junctions.emplace_back(wall_polygon.junctions.front());
|
||||
}
|
||||
wall_polygon.is_closed = true;
|
||||
wall_lines.emplace_back(std::move(wall_polygon)); // add stitched polygons to result
|
||||
}
|
||||
|
|
|
@ -29,8 +29,6 @@ public:
|
|||
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
|
||||
|
@ -44,7 +42,7 @@ public:
|
|||
* \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);
|
||||
WallToolPaths(const Polygons& outline, coord_t bead_width_0, coord_t bead_width_x, size_t inset_count, coord_t wall_0_inset, coordf_t layer_height, const WallToolPathsParams ¶ms);
|
||||
|
||||
/*!
|
||||
* Generates the Toolpaths
|
||||
|
@ -123,14 +121,15 @@ private:
|
|||
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.
|
||||
coordf_t layer_height;
|
||||
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
|
||||
coord_t wall_transition_filter_deviation; //!< The allowed line width deviation induced by filtering
|
||||
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;
|
||||
};
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ Vec2i64 VoronoiUtils::p(const vd_t::vertex_type *node)
|
|||
{
|
||||
const double x = node->x();
|
||||
const double y = node->y();
|
||||
assert(std::isfinite(x) && std::isfinite(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.
|
||||
|
|
|
@ -35,6 +35,11 @@ public:
|
|||
* 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);
|
||||
|
||||
static inline bool is_finite(const VoronoiUtils::vd_t::vertex_type &vertex)
|
||||
{
|
||||
return std::isfinite(vertex.x()) && std::isfinite(vertex.y());
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
|
|
@ -151,6 +151,8 @@ set(lisbslic3r_sources
|
|||
Geometry/Voronoi.hpp
|
||||
Geometry/VoronoiOffset.cpp
|
||||
Geometry/VoronoiOffset.hpp
|
||||
Geometry/VoronoiUtilsCgal.cpp
|
||||
Geometry/VoronoiUtilsCgal.hpp
|
||||
Geometry/VoronoiVisualUtils.hpp
|
||||
Int128.hpp
|
||||
InternalBridgeDetector.cpp
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include "FillBase.hpp"
|
||||
#include "FillRectilinear.hpp"
|
||||
#include "FillLightning.hpp"
|
||||
#include "FillConcentricInternal.hpp"
|
||||
#include "FillConcentric.hpp"
|
||||
|
||||
|
@ -108,6 +109,9 @@ struct SurfaceFill {
|
|||
Surface surface;
|
||||
ExPolygons expolygons;
|
||||
SurfaceFillParams params;
|
||||
// BBS
|
||||
std::vector<size_t> region_id_group;
|
||||
ExPolygons no_overlap_expolygons;
|
||||
};
|
||||
|
||||
// BBS: used to judge whether the internal solid infill area is narrow
|
||||
|
@ -209,8 +213,18 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
|
|||
fill.region_id = region_id;
|
||||
fill.surface = surface;
|
||||
fill.expolygons.emplace_back(std::move(fill.surface.expolygon));
|
||||
} else
|
||||
fill.expolygons.emplace_back(surface.expolygon);
|
||||
//BBS
|
||||
fill.region_id_group.push_back(region_id);
|
||||
fill.no_overlap_expolygons = layerm.fill_no_overlap_expolygons;
|
||||
} else {
|
||||
fill.expolygons.emplace_back(surface.expolygon);
|
||||
//BBS
|
||||
auto t = find(fill.region_id_group.begin(), fill.region_id_group.end(), region_id);
|
||||
if (t == fill.region_id_group.end()) {
|
||||
fill.region_id_group.push_back(region_id);
|
||||
fill.no_overlap_expolygons = union_ex(fill.no_overlap_expolygons, layerm.fill_no_overlap_expolygons);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -336,6 +350,8 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
|
|||
surface_fills.back().region_id = surface_fills[i].region_id;
|
||||
surface_fills.back().surface.surface_type = stInternalSolid;
|
||||
surface_fills.back().surface.thickness = surface_fills[i].surface.thickness;
|
||||
surface_fills.back().region_id_group = surface_fills[i].region_id_group;
|
||||
surface_fills.back().no_overlap_expolygons = surface_fills[i].no_overlap_expolygons;
|
||||
for (size_t j = 0; j < narrow_expolygons_index.size(); j++) {
|
||||
// BBS: move the narrow expolygons to new surface_fills.back();
|
||||
surface_fills.back().expolygons.emplace_back(std::move(surface_fills[i].expolygons[narrow_expolygons_index[j]]));
|
||||
|
@ -373,7 +389,7 @@ void export_group_fills_to_svg(const char *path, const std::vector<SurfaceFill>
|
|||
#endif
|
||||
|
||||
// friend to Layer
|
||||
void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree)
|
||||
void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator)
|
||||
{
|
||||
for (LayerRegion *layerm : m_regions)
|
||||
layerm->fills.clear();
|
||||
|
@ -413,7 +429,8 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
|||
assert(fill_concentric != nullptr);
|
||||
fill_concentric->print_config = &this->object()->print()->config();
|
||||
fill_concentric->print_object_config = &this->object()->config();
|
||||
}
|
||||
} else if (surface_fill.params.pattern == ipLightning)
|
||||
dynamic_cast<FillLightning::Filler*>(f.get())->generator = lightning_generator;
|
||||
|
||||
// calculate flow spacing for infill pattern generation
|
||||
bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.bridge;
|
||||
|
@ -441,6 +458,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
|||
params.anchor_length_max = surface_fill.params.anchor_length_max;
|
||||
params.resolution = resolution;
|
||||
params.use_arachne = surface_fill.params.pattern == ipConcentric;
|
||||
params.layer_height = m_regions[surface_fill.region_id]->layer()->height;
|
||||
|
||||
// BBS
|
||||
params.flow = surface_fill.params.flow;
|
||||
|
|
|
@ -65,6 +65,8 @@ struct FillParams
|
|||
|
||||
// For Concentric infill, to switch between Classic and Arachne.
|
||||
bool use_arachne{ false };
|
||||
// Layer height for Concentric infill with Arachne.
|
||||
coordf_t layer_height { 0.f };
|
||||
|
||||
// BBS
|
||||
Flow flow;
|
||||
|
|
|
@ -83,15 +83,13 @@ void FillConcentric::_fill_surface_single(const FillParams& params,
|
|||
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.min_feature_size = 0.25 * min_nozzle_diameter;
|
||||
input_params.wall_transition_length = 1.0 * min_nozzle_diameter;
|
||||
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);
|
||||
Arachne::WallToolPaths wallToolPaths(polygons, min_spacing, min_spacing, loops_count, 0, params.layer_height, input_params);
|
||||
|
||||
std::vector<Arachne::VariableWidthLines> loops = wallToolPaths.getToolPaths();
|
||||
std::vector<const Arachne::ExtrusionLine*> all_extrusions;
|
||||
|
|
|
@ -27,15 +27,13 @@ void FillConcentricInternal::fill_surface_extrusion(const Surface* surface, cons
|
|||
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.min_feature_size = 0.25 * min_nozzle_diameter;
|
||||
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);
|
||||
Arachne::WallToolPaths wallToolPaths(polygons, min_spacing, min_spacing, loops_count, 0, params.layer_height, input_params);
|
||||
|
||||
std::vector<Arachne::VariableWidthLines> loops = wallToolPaths.getToolPaths();
|
||||
std::vector<const Arachne::ExtrusionLine*> all_extrusions;
|
||||
|
|
|
@ -1,20 +1,25 @@
|
|||
#include "../Print.hpp"
|
||||
#include "../ShortestPath.hpp"
|
||||
|
||||
#include "FillLightning.hpp"
|
||||
#include "Lightning/Generator.hpp"
|
||||
#include "../Surface.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
namespace Slic3r::FillLightning {
|
||||
|
||||
Polylines Filler::fill_surface(const Surface *surface, const FillParams ¶ms)
|
||||
void Filler::_fill_surface_single(
|
||||
const FillParams ¶ms,
|
||||
unsigned int thickness_layers,
|
||||
const std::pair<float, Point> &direction,
|
||||
ExPolygon expolygon,
|
||||
Polylines &polylines_out)
|
||||
{
|
||||
const Layer &layer = generator->getTreesForLayer(this->layer_id);
|
||||
return layer.convertToLines(to_polygons(surface->expolygon), generator->infilll_extrusion_width());
|
||||
const Layer &layer = generator->getTreesForLayer(this->layer_id);
|
||||
Polylines fill_lines = layer.convertToLines(to_polygons(expolygon), scaled<coord_t>(0.5 * this->spacing - this->overlap));
|
||||
|
||||
if (params.dont_connect() || fill_lines.size() <= 1) {
|
||||
append(polylines_out, chain_polylines(std::move(fill_lines)));
|
||||
} else
|
||||
connect_infill(std::move(fill_lines), expolygon, polylines_out, this->spacing, params);
|
||||
}
|
||||
|
||||
void GeneratorDeleter::operator()(Generator *p) {
|
||||
|
|
|
@ -3,6 +3,13 @@
|
|||
|
||||
#include "FillBase.hpp"
|
||||
|
||||
/*
|
||||
* A few modifications based on dba1179(2022.06.10) from Prusa, mainly in Generator.hpp and .cpp:
|
||||
* 1. delete the second parameter(a throw back function) of Generator(), since I didnt find corresponding throw back function in BBS code
|
||||
* 2. those codes that call the functions above
|
||||
* 3. add codes of generating lightning in TreeSupport.cpp
|
||||
*/
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class PrintObject;
|
||||
|
@ -24,8 +31,13 @@ public:
|
|||
Generator *generator { nullptr };
|
||||
protected:
|
||||
Fill* clone() const override { return new Filler(*this); }
|
||||
// Perform the fill.
|
||||
Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override;
|
||||
|
||||
void _fill_surface_single(const FillParams ¶ms,
|
||||
unsigned int thickness_layers,
|
||||
const std::pair<float, Point> &direction,
|
||||
ExPolygon expolygon,
|
||||
Polylines &polylines_out) override;
|
||||
|
||||
// Let the G-code export reoder the infill lines.
|
||||
bool no_sort() const override { return false; }
|
||||
};
|
||||
|
|
|
@ -407,13 +407,15 @@ public:
|
|||
// for the infill pattern, don't cut the corners.
|
||||
// default miterLimt = 3
|
||||
//double miterLimit = 10.;
|
||||
assert(aoffset1 < 0);
|
||||
// FIXME: Resolve properly the cases when it is constructed with aoffset1 = 0 and aoffset2 = 0,
|
||||
// that is used in sample_grid_pattern() for Lightning infill.
|
||||
//assert(aoffset1 < 0);
|
||||
assert(aoffset2 <= 0);
|
||||
assert(aoffset2 == 0 || aoffset2 < aoffset1);
|
||||
// assert(aoffset2 == 0 || aoffset2 < aoffset1);
|
||||
// bool sticks_removed =
|
||||
remove_sticks(polygons_src);
|
||||
// if (sticks_removed) BOOST_LOG_TRIVIAL(error) << "Sticks removed!";
|
||||
polygons_outer = offset(polygons_src, float(aoffset1), ClipperLib::jtMiter, miterLimit);
|
||||
polygons_outer = aoffset1 == 0 ? polygons_src : offset(polygons_src, float(aoffset1), ClipperLib::jtMiter, miterLimit);
|
||||
if (aoffset2 < 0)
|
||||
polygons_inner = shrink(polygons_outer, float(aoffset1 - aoffset2), ClipperLib::jtMiter, miterLimit);
|
||||
// Filter out contours with zero area or small area, contours with 2 points only.
|
||||
|
@ -3053,14 +3055,18 @@ Polylines FillSupportBase::fill_surface(const Surface *surface, const FillParams
|
|||
return polylines_out;
|
||||
}
|
||||
|
||||
Points sample_grid_pattern(const ExPolygon &expolygon, coord_t spacing)
|
||||
// Lightning infill assumes that the distance between any two sampled points is always
|
||||
// at least equal to the value of spacing. To meet this assumption, we need to use
|
||||
// BoundingBox for whole layers instead of bounding box just around processing ExPolygon.
|
||||
// Using just BoundingBox around processing ExPolygon could produce two points closer
|
||||
// than spacing (in cases where two ExPolygon are closer than spacing).
|
||||
Points sample_grid_pattern(const ExPolygon& expolygon, coord_t spacing, const BoundingBox& global_bounding_box)
|
||||
{
|
||||
ExPolygonWithOffset poly_with_offset(expolygon, 0, 0, 0);
|
||||
BoundingBox bounding_box = poly_with_offset.bounding_box_src();
|
||||
std::vector<SegmentedIntersectionLine> segs = slice_region_by_vertical_lines(
|
||||
poly_with_offset,
|
||||
(bounding_box.max.x() - bounding_box.min.x() + spacing - 1) / spacing,
|
||||
bounding_box.min.x(),
|
||||
(global_bounding_box.max.x() - global_bounding_box.min.x() + spacing - 1) / spacing,
|
||||
global_bounding_box.min.x(),
|
||||
spacing);
|
||||
|
||||
Points out;
|
||||
|
@ -3076,17 +3082,17 @@ Points sample_grid_pattern(const ExPolygon &expolygon, coord_t spacing)
|
|||
return out;
|
||||
}
|
||||
|
||||
Points sample_grid_pattern(const ExPolygons &expolygons, coord_t spacing)
|
||||
Points sample_grid_pattern(const ExPolygons& expolygons, coord_t spacing, const BoundingBox& global_bounding_box)
|
||||
{
|
||||
Points out;
|
||||
for (const ExPolygon &expoly : expolygons)
|
||||
append(out, sample_grid_pattern(expoly, spacing));
|
||||
for (const ExPolygon& expoly : expolygons)
|
||||
append(out, sample_grid_pattern(expoly, spacing, global_bounding_box));
|
||||
return out;
|
||||
}
|
||||
|
||||
Points sample_grid_pattern(const Polygons &polygons, coord_t spacing)
|
||||
Points sample_grid_pattern(const Polygons& polygons, coord_t spacing, const BoundingBox& global_bounding_box)
|
||||
{
|
||||
return sample_grid_pattern(union_ex(polygons), spacing);
|
||||
return sample_grid_pattern(union_ex(polygons), spacing, global_bounding_box);
|
||||
}
|
||||
|
||||
void FillMonotonicLineWGapFill::fill_surface_extrusion(const Surface* surface, const FillParams& params, ExtrusionEntitiesPtr& out)
|
||||
|
|
|
@ -132,9 +132,9 @@ private:
|
|||
void fill_surface_by_lines(const Surface* surface, const FillParams& params, Polylines& polylines_out);
|
||||
};
|
||||
|
||||
Points sample_grid_pattern(const ExPolygon &expolygon, coord_t spacing);
|
||||
Points sample_grid_pattern(const ExPolygons &expolygons, coord_t spacing);
|
||||
Points sample_grid_pattern(const Polygons &polygons, coord_t spacing);
|
||||
Points sample_grid_pattern(const ExPolygon& expolygon, coord_t spacing, const BoundingBox& global_bounding_box);
|
||||
Points sample_grid_pattern(const ExPolygons& expolygons, coord_t spacing, const BoundingBox& global_bounding_box);
|
||||
Points sample_grid_pattern(const Polygons& polygons, coord_t spacing, const BoundingBox& global_bounding_box);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
|
|
|
@ -5,48 +5,99 @@
|
|||
#include "../FillRectilinear.hpp"
|
||||
#include "../../ClipperUtils.hpp"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
|
||||
#ifdef LIGHTNING_DISTANCE_FIELD_DEBUG_OUTPUT
|
||||
#include "../../SVG.hpp"
|
||||
#endif
|
||||
|
||||
namespace Slic3r::FillLightning
|
||||
{
|
||||
|
||||
constexpr coord_t radius_per_cell_size = 6; // The cell-size should be small compared to the radius, but not so small as to be inefficient.
|
||||
|
||||
DistanceField::DistanceField(const coord_t& radius, const Polygons& current_outline, const Polygons& current_overhang) :
|
||||
m_cell_size(radius / radius_per_cell_size),
|
||||
m_supporting_radius(radius)
|
||||
#ifdef LIGHTNING_DISTANCE_FIELD_DEBUG_OUTPUT
|
||||
void export_distance_field_to_svg(const std::string &path, const Polygons &outline, const Polygons &overhang, const std::list<DistanceField::UnsupportedCell> &unsupported_points, const Points &points = {})
|
||||
{
|
||||
m_supporting_radius2 = double(radius) * double(radius);
|
||||
coordf_t stroke_width = scaled<coordf_t>(0.01);
|
||||
BoundingBox bbox = get_extents(outline);
|
||||
|
||||
bbox.offset(SCALED_EPSILON);
|
||||
SVG svg(path, bbox);
|
||||
svg.draw_outline(outline, "green", stroke_width);
|
||||
svg.draw_outline(overhang, "blue", stroke_width);
|
||||
|
||||
for (const DistanceField::UnsupportedCell &cell : unsupported_points)
|
||||
svg.draw(cell.loc, "cyan", coord_t(stroke_width));
|
||||
|
||||
for (const Point &pt : points)
|
||||
svg.draw(pt, "red", coord_t(stroke_width));
|
||||
}
|
||||
#endif
|
||||
|
||||
DistanceField::DistanceField(const coord_t& radius, const Polygons& current_outline, const BoundingBox& current_outlines_bbox, const Polygons& current_overhang) :
|
||||
m_cell_size(radius / radius_per_cell_size),
|
||||
m_supporting_radius(radius),
|
||||
m_unsupported_points_bbox(current_outlines_bbox)
|
||||
{
|
||||
m_supporting_radius2 = Slic3r::sqr(int64_t(radius));
|
||||
// Sample source polygons with a regular grid sampling pattern.
|
||||
for (const ExPolygon &expoly : union_ex(current_outline)) {
|
||||
for (const Point &p : sample_grid_pattern(expoly, m_cell_size)) {
|
||||
// Find a squared distance to the source expolygon boundary.
|
||||
double d2 = std::numeric_limits<double>::max();
|
||||
for (size_t icontour = 0; icontour <= expoly.holes.size(); ++ icontour) {
|
||||
const Polygon &contour = icontour == 0 ? expoly.contour : expoly.holes[icontour - 1];
|
||||
if (contour.size() > 2) {
|
||||
Point prev = contour.points.back();
|
||||
for (const Point &p2 : contour.points) {
|
||||
d2 = std::min(d2, Line::distance_to_squared(p, prev, p2));
|
||||
prev = p2;
|
||||
const BoundingBox overhang_bbox = get_extents(current_overhang);
|
||||
for (const ExPolygon &expoly : union_ex(current_overhang)) {
|
||||
const Points sampled_points = sample_grid_pattern(expoly, m_cell_size, overhang_bbox);
|
||||
const size_t unsupported_points_prev_size = m_unsupported_points.size();
|
||||
m_unsupported_points.resize(unsupported_points_prev_size + sampled_points.size());
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, sampled_points.size()), [&self = *this, &expoly = std::as_const(expoly), &sampled_points = std::as_const(sampled_points), &unsupported_points_prev_size = std::as_const(unsupported_points_prev_size)](const tbb::blocked_range<size_t> &range) -> void {
|
||||
for (size_t sp_idx = range.begin(); sp_idx < range.end(); ++sp_idx) {
|
||||
const Point &sp = sampled_points[sp_idx];
|
||||
// Find a squared distance to the source expolygon boundary.
|
||||
double d2 = std::numeric_limits<double>::max();
|
||||
for (size_t icontour = 0; icontour <= expoly.holes.size(); ++icontour) {
|
||||
const Polygon &contour = icontour == 0 ? expoly.contour : expoly.holes[icontour - 1];
|
||||
if (contour.size() > 2) {
|
||||
Point prev = contour.points.back();
|
||||
for (const Point &p2 : contour.points) {
|
||||
d2 = std::min(d2, Line::distance_to_squared(sp, prev, p2));
|
||||
prev = p2;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.m_unsupported_points[unsupported_points_prev_size + sp_idx] = {sp, coord_t(std::sqrt(d2))};
|
||||
assert(self.m_unsupported_points_bbox.contains(sp));
|
||||
}
|
||||
m_unsupported_points.emplace_back(p, sqrt(d2));
|
||||
}
|
||||
}); // end of parallel_for
|
||||
}
|
||||
m_unsupported_points.sort([&radius](const UnsupportedCell &a, const UnsupportedCell &b) {
|
||||
std::stable_sort(m_unsupported_points.begin(), m_unsupported_points.end(), [&radius](const UnsupportedCell &a, const UnsupportedCell &b) {
|
||||
constexpr coord_t prime_for_hash = 191;
|
||||
return std::abs(b.dist_to_boundary - a.dist_to_boundary) > radius ?
|
||||
a.dist_to_boundary < b.dist_to_boundary :
|
||||
(PointHash{}(a.loc) % prime_for_hash) < (PointHash{}(b.loc) % prime_for_hash);
|
||||
});
|
||||
for (auto it = m_unsupported_points.begin(); it != m_unsupported_points.end(); ++it) {
|
||||
UnsupportedCell& cell = *it;
|
||||
m_unsupported_points_grid.emplace(Point{ cell.loc.x() / m_cell_size, cell.loc.y() / m_cell_size }, it);
|
||||
|
||||
m_unsupported_points_erased.resize(m_unsupported_points.size());
|
||||
std::fill(m_unsupported_points_erased.begin(), m_unsupported_points_erased.end(), false);
|
||||
|
||||
m_unsupported_points_grid.initialize(m_unsupported_points, [&self = std::as_const(*this)](const Point &p) -> Point { return self.to_grid_point(p); });
|
||||
|
||||
// Because the distance between two points is at least one axis equal to m_cell_size, every cell
|
||||
// in m_unsupported_points_grid contains exactly one point.
|
||||
assert(m_unsupported_points.size() == m_unsupported_points_grid.size());
|
||||
|
||||
#ifdef LIGHTNING_DISTANCE_FIELD_DEBUG_OUTPUT
|
||||
{
|
||||
static int iRun = 0;
|
||||
export_distance_field_to_svg(debug_out_path("FillLightning-DistanceField-%d.svg", iRun++), current_outline, current_overhang, m_unsupported_points);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void DistanceField::update(const Point& to_node, const Point& added_leaf)
|
||||
{
|
||||
std::ofstream out1("z:/misc/lightning.txt", std::ios::app);
|
||||
out1 << m_unsupported_points.size() << std::endl;
|
||||
out1.close();
|
||||
|
||||
Vec2d v = (added_leaf - to_node).cast<double>();
|
||||
auto l2 = v.squaredNorm();
|
||||
Vec2d extent = Vec2d(-v.y(), v.x()) * m_supporting_radius / sqrt(l2);
|
||||
|
@ -60,17 +111,24 @@ void DistanceField::update(const Point& to_node, const Point& added_leaf)
|
|||
grid.merge(to_node + iextent);
|
||||
grid.merge(added_leaf - iextent);
|
||||
grid.merge(added_leaf + iextent);
|
||||
grid.min /= m_cell_size;
|
||||
grid.max /= m_cell_size;
|
||||
|
||||
// Clip grid by m_unsupported_points_bbox. Mainly to ensure that grid.min is a non-negative value.
|
||||
grid.min.x() = std::max(grid.min.x(), m_unsupported_points_bbox.min.x());
|
||||
grid.min.y() = std::max(grid.min.y(), m_unsupported_points_bbox.min.y());
|
||||
grid.max.x() = std::min(grid.max.x(), m_unsupported_points_bbox.max.x());
|
||||
grid.max.y() = std::min(grid.max.y(), m_unsupported_points_bbox.max.y());
|
||||
|
||||
grid.min = this->to_grid_point(grid.min);
|
||||
grid.max = this->to_grid_point(grid.max);
|
||||
}
|
||||
|
||||
Point grid_addr;
|
||||
Point grid_loc;
|
||||
for (coord_t row = grid.min.y(); row <= grid.max.y(); ++ row) {
|
||||
grid_loc.y() = row * m_cell_size;
|
||||
for (coord_t col = grid.min.x(); col <= grid.max.y(); ++ col) {
|
||||
grid_loc.x() = col * m_cell_size;
|
||||
for (grid_addr.y() = grid.min.y(); grid_addr.y() <= grid.max.y(); ++grid_addr.y()) {
|
||||
for (grid_addr.x() = grid.min.x(); grid_addr.x() <= grid.max.x(); ++grid_addr.x()) {
|
||||
grid_loc = this->from_grid_point(grid_addr);
|
||||
// Test inside a circle at the new leaf.
|
||||
if ((grid_loc - added_leaf).cast<double>().squaredNorm() > m_supporting_radius2) {
|
||||
if ((grid_loc - added_leaf).cast<int64_t>().squaredNorm() > m_supporting_radius2) {
|
||||
// Not inside a circle at the end of the new leaf.
|
||||
// Test inside a rotated rectangle.
|
||||
Vec2d vx = (grid_loc - to_node).cast<double>();
|
||||
|
@ -84,10 +142,29 @@ void DistanceField::update(const Point& to_node, const Point& added_leaf)
|
|||
}
|
||||
// Inside a circle at the end of the new leaf, or inside a rotated rectangle.
|
||||
// Remove unsupported leafs at this grid location.
|
||||
if (auto it = m_unsupported_points_grid.find(grid_loc); it != m_unsupported_points_grid.end()) {
|
||||
std::list<UnsupportedCell>::iterator& list_it = it->second;
|
||||
UnsupportedCell& cell = *list_it;
|
||||
if ((cell.loc - added_leaf).cast<double>().squaredNorm() <= m_supporting_radius2) {
|
||||
if (const size_t cell_idx = m_unsupported_points_grid.find_cell_idx(grid_addr); cell_idx != std::numeric_limits<size_t>::max()) {
|
||||
const UnsupportedCell &cell = m_unsupported_points[cell_idx];
|
||||
if ((cell.loc - added_leaf).cast<int64_t>().squaredNorm() <= m_supporting_radius2) {
|
||||
m_unsupported_points_erased[cell_idx] = true;
|
||||
m_unsupported_points_grid.mark_erased(grid_addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
void DistanceField::update(const Point &to_node, const Point &added_leaf)
|
||||
{
|
||||
const Point supporting_radius_point(m_supporting_radius, m_supporting_radius);
|
||||
const BoundingBox grid(this->to_grid_point(added_leaf - supporting_radius_point), this->to_grid_point(added_leaf + supporting_radius_point));
|
||||
|
||||
for (coord_t grid_y = grid.min.y(); grid_y <= grid.max.y(); ++grid_y) {
|
||||
for (coord_t grid_x = grid.min.x(); grid_x <= grid.max.x(); ++grid_x) {
|
||||
if (auto it = m_unsupported_points_grid.find({grid_x, grid_y}); it != m_unsupported_points_grid.end()) {
|
||||
std::list<UnsupportedCell>::iterator &list_it = it->second;
|
||||
UnsupportedCell &cell = *list_it;
|
||||
if ((cell.loc - added_leaf).cast<int64_t>().squaredNorm() <= m_supporting_radius2) {
|
||||
m_unsupported_points.erase(list_it);
|
||||
m_unsupported_points_grid.erase(it);
|
||||
}
|
||||
|
@ -95,5 +172,6 @@ void DistanceField::update(const Point& to_node, const Point& added_leaf)
|
|||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace Slic3r::FillLightning
|
||||
|
|
|
@ -4,9 +4,12 @@
|
|||
#ifndef LIGHTNING_DISTANCE_FIELD_H
|
||||
#define LIGHTNING_DISTANCE_FIELD_H
|
||||
|
||||
#include "../../BoundingBox.hpp"
|
||||
#include "../../Point.hpp"
|
||||
#include "../../Polygon.hpp"
|
||||
|
||||
//#define LIGHTNING_DISTANCE_FIELD_DEBUG_OUTPUT
|
||||
|
||||
namespace Slic3r::FillLightning
|
||||
{
|
||||
|
||||
|
@ -29,7 +32,7 @@ public:
|
|||
* \param current_overhang The overhang that needs to be supported on this
|
||||
* layer.
|
||||
*/
|
||||
DistanceField(const coord_t& radius, const Polygons& current_outline, const Polygons& current_overhang);
|
||||
DistanceField(const coord_t& radius, const Polygons& current_outline, const BoundingBox& current_outlines_bbox, const Polygons& current_overhang);
|
||||
|
||||
/*!
|
||||
* Gets the next unsupported location to be supported by a new branch.
|
||||
|
@ -37,11 +40,17 @@ public:
|
|||
* \return ``true`` if successful, or ``false`` if there are no more points
|
||||
* to consider.
|
||||
*/
|
||||
bool tryGetNextPoint(Point* p) const {
|
||||
if (m_unsupported_points.empty())
|
||||
return false;
|
||||
*p = m_unsupported_points.front().loc;
|
||||
return true;
|
||||
bool tryGetNextPoint(Point *out_unsupported_location, size_t *out_unsupported_cell_idx, const size_t start_idx = 0) const
|
||||
{
|
||||
for (size_t point_idx = start_idx; point_idx < m_unsupported_points.size(); ++point_idx) {
|
||||
if (!m_unsupported_points_erased[point_idx]) {
|
||||
*out_unsupported_cell_idx = point_idx;
|
||||
*out_unsupported_location = m_unsupported_points[point_idx].loc;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -69,14 +78,13 @@ protected:
|
|||
* branch of a tree.
|
||||
*/
|
||||
coord_t m_supporting_radius;
|
||||
double m_supporting_radius2;
|
||||
int64_t m_supporting_radius2;
|
||||
|
||||
/*!
|
||||
* Represents a small discrete area of infill that needs to be supported.
|
||||
*/
|
||||
struct UnsupportedCell
|
||||
{
|
||||
UnsupportedCell(Point loc, coord_t dist_to_boundary) : loc(loc), dist_to_boundary(dist_to_boundary) {}
|
||||
// The position of the center of this cell.
|
||||
Point loc;
|
||||
// How far this cell is removed from the ``current_outline`` polygon, the edge of the infill area.
|
||||
|
@ -86,13 +94,114 @@ protected:
|
|||
/*!
|
||||
* Cells which still need to be supported at some point.
|
||||
*/
|
||||
std::list<UnsupportedCell> m_unsupported_points;
|
||||
std::vector<UnsupportedCell> m_unsupported_points;
|
||||
std::vector<bool> m_unsupported_points_erased;
|
||||
|
||||
/*!
|
||||
* BoundingBox of all points in m_unsupported_points. Used for mapping of sign integer numbers to positive integer numbers.
|
||||
*/
|
||||
const BoundingBox m_unsupported_points_bbox;
|
||||
|
||||
/*!
|
||||
* Links the unsupported points to a grid point, so that we can quickly look
|
||||
* up the cell belonging to a certain position in the grid.
|
||||
*/
|
||||
std::unordered_map<Point, std::list<UnsupportedCell>::iterator, PointHash> m_unsupported_points_grid;
|
||||
|
||||
class UnsupportedPointsGrid
|
||||
{
|
||||
public:
|
||||
UnsupportedPointsGrid() = default;
|
||||
void initialize(const std::vector<UnsupportedCell> &unsupported_points, const std::function<Point(const Point &)> &map_cell_to_grid)
|
||||
{
|
||||
if (unsupported_points.empty())
|
||||
return;
|
||||
|
||||
BoundingBox unsupported_points_bbox;
|
||||
for (const UnsupportedCell &cell : unsupported_points)
|
||||
unsupported_points_bbox.merge(cell.loc);
|
||||
|
||||
m_size = unsupported_points.size();
|
||||
m_grid_range = BoundingBox(map_cell_to_grid(unsupported_points_bbox.min), map_cell_to_grid(unsupported_points_bbox.max));
|
||||
m_grid_size = m_grid_range.size() + Point::Ones();
|
||||
|
||||
m_data.assign(m_grid_size.y() * m_grid_size.x(), std::numeric_limits<size_t>::max());
|
||||
m_data_erased.assign(m_grid_size.y() * m_grid_size.x(), true);
|
||||
|
||||
for (size_t cell_idx = 0; cell_idx < unsupported_points.size(); ++cell_idx) {
|
||||
const size_t flat_idx = map_to_flat_array(map_cell_to_grid(unsupported_points[cell_idx].loc));
|
||||
assert(m_data[flat_idx] == std::numeric_limits<size_t>::max());
|
||||
m_data[flat_idx] = cell_idx;
|
||||
m_data_erased[flat_idx] = false;
|
||||
}
|
||||
}
|
||||
|
||||
size_t size() const { return m_size; }
|
||||
|
||||
size_t find_cell_idx(const Point &grid_addr)
|
||||
{
|
||||
if (!m_grid_range.contains(grid_addr))
|
||||
return std::numeric_limits<size_t>::max();
|
||||
|
||||
if (const size_t flat_idx = map_to_flat_array(grid_addr); !m_data_erased[flat_idx]) {
|
||||
assert(m_data[flat_idx] != std::numeric_limits<size_t>::max());
|
||||
return m_data[flat_idx];
|
||||
}
|
||||
|
||||
return std::numeric_limits<size_t>::max();
|
||||
}
|
||||
|
||||
void mark_erased(const Point &grid_addr)
|
||||
{
|
||||
assert(m_grid_range.contains(grid_addr));
|
||||
if (!m_grid_range.contains(grid_addr))
|
||||
return;
|
||||
|
||||
const size_t flat_idx = map_to_flat_array(grid_addr);
|
||||
assert(!m_data_erased[flat_idx] && m_data[flat_idx] != std::numeric_limits<size_t>::max());
|
||||
assert(m_size != 0);
|
||||
|
||||
m_data_erased[flat_idx] = true;
|
||||
--m_size;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t m_size = 0;
|
||||
|
||||
BoundingBox m_grid_range;
|
||||
Point m_grid_size;
|
||||
|
||||
std::vector<size_t> m_data;
|
||||
std::vector<bool> m_data_erased;
|
||||
|
||||
inline size_t map_to_flat_array(const Point &loc) const
|
||||
{
|
||||
const Point offset_loc = loc - m_grid_range.min;
|
||||
const size_t flat_idx = m_grid_size.x() * offset_loc.y() + offset_loc.x();
|
||||
assert(offset_loc.x() >= 0 && offset_loc.y() >= 0);
|
||||
assert(flat_idx < size_t(m_grid_size.y() * m_grid_size.x()));
|
||||
return flat_idx;
|
||||
}
|
||||
};
|
||||
|
||||
UnsupportedPointsGrid m_unsupported_points_grid;
|
||||
|
||||
/*!
|
||||
* Maps the point to the grid coordinates.
|
||||
*/
|
||||
Point to_grid_point(const Point &point) const {
|
||||
return (point - m_unsupported_points_bbox.min) / m_cell_size;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Maps the point to the grid coordinates.
|
||||
*/
|
||||
Point from_grid_point(const Point &point) const {
|
||||
return point * m_cell_size + m_unsupported_points_bbox.min;
|
||||
}
|
||||
|
||||
#ifdef LIGHTNING_DISTANCE_FIELD_DEBUG_OUTPUT
|
||||
friend void export_distance_field_to_svg(const std::string &path, const Polygons &outline, const Polygons &overhang, const std::list<DistanceField::UnsupportedCell> &unsupported_points, const Points &points);
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace Slic3r::FillLightning
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
#include "../../ClipperUtils.hpp"
|
||||
#include "../../Layer.hpp"
|
||||
#include "../../Print.hpp"
|
||||
#include "../../Surface.hpp"
|
||||
|
||||
#include "ExPolygon.hpp"
|
||||
|
||||
/* Possible future tasks/optimizations,etc.:
|
||||
* - Improve connecting heuristic to favor connecting to shorter trees
|
||||
|
@ -23,6 +24,43 @@
|
|||
* - Move more complex computations from Generator constructor to elsewhere.
|
||||
*/
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
static std::string get_svg_filename(std::string layer_nr_or_z, std::string tag = "bbl_ts")
|
||||
{
|
||||
static bool rand_init = false;
|
||||
|
||||
if (!rand_init) {
|
||||
srand(time(NULL));
|
||||
rand_init = true;
|
||||
}
|
||||
|
||||
int rand_num = rand() % 1000000;
|
||||
//makedir("./SVG");
|
||||
std::string prefix = "./SVG/";
|
||||
std::string suffix = ".svg";
|
||||
return prefix + tag + "_" + layer_nr_or_z /*+ "_" + std::to_string(rand_num)*/ + suffix;
|
||||
}
|
||||
|
||||
Slic3r::SVG draw_two_overhangs_to_svg(size_t ts_layer, const ExPolygons& overhangs1, const ExPolygons& overhangs2)
|
||||
{
|
||||
//if (overhangs1.empty() && overhangs2.empty())
|
||||
// return ;
|
||||
BoundingBox bbox1 = get_extents(overhangs1);
|
||||
BoundingBox bbox2 = get_extents(overhangs2);
|
||||
bbox1.merge(bbox2);
|
||||
|
||||
Slic3r::SVG svg(get_svg_filename(std::to_string(ts_layer), "two_overhangs_generator"), bbox1);
|
||||
//if (!svg.is_opened()) return;
|
||||
|
||||
svg.draw(union_ex(overhangs1), "blue");
|
||||
svg.draw(union_ex(overhangs2), "red");
|
||||
|
||||
return svg;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
namespace Slic3r::FillLightning {
|
||||
|
||||
Generator::Generator(const PrintObject &print_object)
|
||||
|
@ -32,13 +70,46 @@ Generator::Generator(const PrintObject &print_object)
|
|||
const PrintRegionConfig ®ion_config = print_object.shared_regions()->all_regions.front()->config();
|
||||
const std::vector<double> &nozzle_diameters = print_config.nozzle_diameter.values;
|
||||
double max_nozzle_diameter = *std::max_element(nozzle_diameters.begin(), nozzle_diameters.end());
|
||||
// const int sparse_infill_filament = region_config.sparse_infill_filament.value;
|
||||
// const int infill_extruder = region_config.infill_extruder.value;
|
||||
const double default_infill_extrusion_width = Flow::auto_extrusion_width(FlowRole::frInfill, float(max_nozzle_diameter));
|
||||
// Note: There's not going to be a layer below the first one, so the 'initial layer height' doesn't have to be taken into account.
|
||||
const double layer_thickness = object_config.layer_height;
|
||||
const double layer_thickness = scaled<double>(object_config.layer_height.value);
|
||||
|
||||
//m_infill_extrusion_width = scaled<float>(region_config.infill_extrusion_width.percent ? default_infill_extrusion_width * 0.01 * region_config.infill_extrusion_width : region_config.infill_extrusion_width);
|
||||
//m_supporting_radius = coord_t(m_infill_extrusion_width) * 100 / coord_t(region_config.fill_density.value);
|
||||
m_infill_extrusion_width = scaled<float>(region_config.sparse_infill_line_width.value);
|
||||
m_supporting_radius = coord_t(m_infill_extrusion_width) * 100 / region_config.sparse_infill_density;
|
||||
|
||||
const double lightning_infill_overhang_angle = M_PI / 4; // 45 degrees
|
||||
const double lightning_infill_prune_angle = M_PI / 4; // 45 degrees
|
||||
const double lightning_infill_straightening_angle = M_PI / 4; // 45 degrees
|
||||
m_wall_supporting_radius = coord_t(layer_thickness * std::tan(lightning_infill_overhang_angle));
|
||||
m_prune_length = coord_t(layer_thickness * std::tan(lightning_infill_prune_angle));
|
||||
m_straightening_max_distance = coord_t(layer_thickness * std::tan(lightning_infill_straightening_angle));
|
||||
|
||||
generateInitialInternalOverhangs(print_object);
|
||||
generateTrees(print_object);
|
||||
}
|
||||
|
||||
Generator::Generator(PrintObject* m_object, std::vector<Polygons>& contours, std::vector<Polygons>& overhangs, float density)
|
||||
{
|
||||
const PrintConfig &print_config = m_object->print()->config();
|
||||
const PrintObjectConfig &object_config = m_object->config();
|
||||
const PrintRegionConfig ®ion_config = m_object->shared_regions()->all_regions.front()->config();
|
||||
const std::vector<double> &nozzle_diameters = print_config.nozzle_diameter.values;
|
||||
double max_nozzle_diameter = *std::max_element(nozzle_diameters.begin(), nozzle_diameters.end());
|
||||
// const int infill_extruder = region_config.infill_extruder.value;
|
||||
const double default_infill_extrusion_width = Flow::auto_extrusion_width(FlowRole::frInfill, float(max_nozzle_diameter));
|
||||
// Note: There's not going to be a layer below the first one, so the 'initial layer height' doesn't have to be taken into account.
|
||||
const double layer_thickness = scaled<double>(object_config.layer_height.value);
|
||||
|
||||
m_infill_extrusion_width = scaled<float>(region_config.sparse_infill_line_width.value);
|
||||
m_supporting_radius = scaled<coord_t>(m_infill_extrusion_width * 0.001 / region_config.sparse_infill_density);
|
||||
//m_supporting_radius: against to the density of lightning, failures may happen if set to high density
|
||||
//higher density lightning makes support harder, more time-consuming on computing and printing, but more reliable on supporting overhangs
|
||||
//lower density lightning performs opposite
|
||||
//TODO: decide whether enable density controller in advanced options or not
|
||||
density = std::max(0.15f, density);
|
||||
m_supporting_radius = coord_t(m_infill_extrusion_width) / density;
|
||||
|
||||
const double lightning_infill_overhang_angle = M_PI / 4; // 45 degrees
|
||||
const double lightning_infill_prune_angle = M_PI / 4; // 45 degrees
|
||||
|
@ -47,26 +118,33 @@ Generator::Generator(const PrintObject &print_object)
|
|||
m_prune_length = layer_thickness * std::tan(lightning_infill_prune_angle);
|
||||
m_straightening_max_distance = layer_thickness * std::tan(lightning_infill_straightening_angle);
|
||||
|
||||
generateInitialInternalOverhangs(print_object);
|
||||
generateTrees(print_object);
|
||||
m_overhang_per_layer = overhangs;
|
||||
|
||||
generateTreesforSupport(contours);
|
||||
|
||||
//for (size_t i = 0; i < overhangs.size(); i++)
|
||||
//{
|
||||
// auto svg = draw_two_overhangs_to_svg(i, to_expolygons(contours[i]), to_expolygons(overhangs[i]));
|
||||
// for (auto& root : m_lightning_layers[i].tree_roots)
|
||||
// root->draw_tree(svg);
|
||||
//}
|
||||
}
|
||||
|
||||
void Generator::generateInitialInternalOverhangs(const PrintObject &print_object)
|
||||
{
|
||||
m_overhang_per_layer.resize(print_object.layers().size());
|
||||
const float infill_wall_offset = - m_infill_extrusion_width;
|
||||
|
||||
Polygons infill_area_above;
|
||||
//Iterate from top to bottom, to subtract the overhang areas above from the overhang areas on the layer below, to get only overhang in the top layer where it is overhanging.
|
||||
for (int layer_nr = print_object.layers().size() - 1; layer_nr >= 0; layer_nr--) {
|
||||
for (int layer_nr = int(print_object.layers().size()) - 1; layer_nr >= 0; --layer_nr) {
|
||||
Polygons infill_area_here;
|
||||
for (const LayerRegion* layerm : print_object.get_layer(layer_nr)->regions())
|
||||
for (const Surface& surface : layerm->fill_surfaces.surfaces)
|
||||
if (surface.surface_type == stInternal)
|
||||
append(infill_area_here, offset(surface.expolygon, infill_wall_offset));
|
||||
if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid)
|
||||
append(infill_area_here, to_polygons(surface.expolygon));
|
||||
|
||||
//Remove the part of the infill area that is already supported by the walls.
|
||||
Polygons overhang = diff(offset(infill_area_here, -m_wall_supporting_radius), infill_area_above);
|
||||
Polygons overhang = diff(offset(infill_area_here, -float(m_wall_supporting_radius)), infill_area_above);
|
||||
|
||||
m_overhang_per_layer[layer_nr] = overhang;
|
||||
infill_area_above = std::move(infill_area_here);
|
||||
|
@ -82,16 +160,16 @@ const Layer& Generator::getTreesForLayer(const size_t& layer_id) const
|
|||
void Generator::generateTrees(const PrintObject &print_object)
|
||||
{
|
||||
m_lightning_layers.resize(print_object.layers().size());
|
||||
const coord_t infill_wall_offset = - m_infill_extrusion_width;
|
||||
|
||||
bboxs.resize(print_object.layers().size());
|
||||
std::vector<Polygons> infill_outlines(print_object.layers().size(), Polygons());
|
||||
|
||||
// For-each layer from top to bottom:
|
||||
for (int layer_id = print_object.layers().size() - 1; layer_id >= 0; layer_id--)
|
||||
for (int layer_id = int(print_object.layers().size()) - 1; layer_id >= 0; layer_id--) {
|
||||
for (const LayerRegion *layerm : print_object.get_layer(layer_id)->regions())
|
||||
for (const Surface &surface : layerm->fill_surfaces.surfaces)
|
||||
if (surface.surface_type == stInternal)
|
||||
append(infill_outlines[layer_id], offset(surface.expolygon, infill_wall_offset));
|
||||
if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid)
|
||||
append(infill_outlines[layer_id], to_polygons(surface.expolygon));
|
||||
}
|
||||
|
||||
// For various operations its beneficial to quickly locate nearby features on the polygon:
|
||||
const size_t top_layer_id = print_object.layers().size() - 1;
|
||||
|
@ -99,23 +177,77 @@ void Generator::generateTrees(const PrintObject &print_object)
|
|||
outlines_locator.create(infill_outlines[top_layer_id], locator_cell_size);
|
||||
|
||||
// For-each layer from top to bottom:
|
||||
for (int layer_id = top_layer_id; layer_id >= 0; layer_id--)
|
||||
{
|
||||
Layer& current_lightning_layer = m_lightning_layers[layer_id];
|
||||
Polygons& current_outlines = infill_outlines[layer_id];
|
||||
for (int layer_id = int(top_layer_id); layer_id >= 0; layer_id--) {
|
||||
Layer ¤t_lightning_layer = m_lightning_layers[layer_id];
|
||||
const Polygons ¤t_outlines = infill_outlines[layer_id];
|
||||
const BoundingBox ¤t_outlines_bbox = get_extents(current_outlines);
|
||||
|
||||
bboxs[layer_id] = get_extents(current_outlines);
|
||||
|
||||
// register all trees propagated from the previous layer as to-be-reconnected
|
||||
std::vector<NodeSPtr> to_be_reconnected_tree_roots = current_lightning_layer.tree_roots;
|
||||
|
||||
current_lightning_layer.generateNewTrees(m_overhang_per_layer[layer_id], current_outlines, outlines_locator, m_supporting_radius, m_wall_supporting_radius);
|
||||
current_lightning_layer.reconnectRoots(to_be_reconnected_tree_roots, current_outlines, outlines_locator, m_supporting_radius, m_wall_supporting_radius);
|
||||
current_lightning_layer.generateNewTrees(m_overhang_per_layer[layer_id], current_outlines, current_outlines_bbox, outlines_locator, m_supporting_radius, m_wall_supporting_radius);
|
||||
current_lightning_layer.reconnectRoots(to_be_reconnected_tree_roots, current_outlines, current_outlines_bbox, outlines_locator, m_supporting_radius, m_wall_supporting_radius);
|
||||
|
||||
// Initialize trees for next lower layer from the current one.
|
||||
if (layer_id == 0)
|
||||
return;
|
||||
|
||||
const Polygons& below_outlines = infill_outlines[layer_id - 1];
|
||||
outlines_locator.set_bbox(get_extents(below_outlines).inflated(SCALED_EPSILON));
|
||||
const Polygons &below_outlines = infill_outlines[layer_id - 1];
|
||||
BoundingBox below_outlines_bbox = get_extents(below_outlines).inflated(SCALED_EPSILON);
|
||||
if (const BoundingBox &outlines_locator_bbox = outlines_locator.bbox(); outlines_locator_bbox.defined)
|
||||
below_outlines_bbox.merge(outlines_locator_bbox);
|
||||
|
||||
if (!current_lightning_layer.tree_roots.empty())
|
||||
below_outlines_bbox.merge(get_extents(current_lightning_layer.tree_roots).inflated(SCALED_EPSILON));
|
||||
|
||||
outlines_locator.set_bbox(below_outlines_bbox);
|
||||
outlines_locator.create(below_outlines, locator_cell_size);
|
||||
|
||||
std::vector<NodeSPtr>& lower_trees = m_lightning_layers[layer_id - 1].tree_roots;
|
||||
for (auto& tree : current_lightning_layer.tree_roots)
|
||||
tree->propagateToNextLayer(lower_trees, below_outlines, outlines_locator, m_prune_length, m_straightening_max_distance, locator_cell_size / 2);
|
||||
}
|
||||
}
|
||||
|
||||
void Generator::generateTreesforSupport(std::vector<Polygons>& contours)
|
||||
{
|
||||
m_lightning_layers.resize(contours.size());
|
||||
bboxs.resize(contours.size());
|
||||
|
||||
// For various operations its beneficial to quickly locate nearby features on the polygon:
|
||||
const size_t top_layer_id = contours.size() - 1;
|
||||
EdgeGrid::Grid outlines_locator(get_extents(contours[top_layer_id]).inflated(SCALED_EPSILON));
|
||||
outlines_locator.create(contours[top_layer_id], locator_cell_size);
|
||||
|
||||
// For-each layer from top to bottom:
|
||||
for (int layer_id = int(top_layer_id); layer_id >= 0; layer_id--) {
|
||||
Layer& current_lightning_layer = m_lightning_layers[layer_id];
|
||||
const Polygons& current_outlines = contours[layer_id];
|
||||
const BoundingBox& current_outlines_bbox = get_extents(current_outlines);
|
||||
|
||||
bboxs[layer_id] = get_extents(current_outlines);
|
||||
|
||||
// register all trees propagated from the previous layer as to-be-reconnected
|
||||
std::vector<NodeSPtr> to_be_reconnected_tree_roots = current_lightning_layer.tree_roots;
|
||||
|
||||
current_lightning_layer.generateNewTrees(m_overhang_per_layer[layer_id], current_outlines, current_outlines_bbox, outlines_locator, m_supporting_radius, m_wall_supporting_radius);
|
||||
current_lightning_layer.reconnectRoots(to_be_reconnected_tree_roots, current_outlines, current_outlines_bbox, outlines_locator, m_supporting_radius, m_wall_supporting_radius);
|
||||
|
||||
// Initialize trees for next lower layer from the current one.
|
||||
if (layer_id == 0)
|
||||
return;
|
||||
|
||||
const Polygons& below_outlines = contours[layer_id - 1];
|
||||
BoundingBox below_outlines_bbox = get_extents(below_outlines).inflated(SCALED_EPSILON);
|
||||
if (const BoundingBox& outlines_locator_bbox = outlines_locator.bbox(); outlines_locator_bbox.defined)
|
||||
below_outlines_bbox.merge(outlines_locator_bbox);
|
||||
|
||||
if (!current_lightning_layer.tree_roots.empty())
|
||||
below_outlines_bbox.merge(get_extents(current_lightning_layer.tree_roots).inflated(SCALED_EPSILON));
|
||||
|
||||
outlines_locator.set_bbox(below_outlines_bbox);
|
||||
outlines_locator.create(below_outlines, locator_cell_size);
|
||||
|
||||
std::vector<NodeSPtr>& lower_trees = m_lightning_layers[layer_id - 1].tree_roots;
|
||||
|
|
|
@ -43,9 +43,8 @@ public:
|
|||
* This generator will pre-compute things in preparation of generating
|
||||
* Lightning Infill for the infill areas in that mesh. The infill areas must
|
||||
* already be calculated at this point.
|
||||
* \param mesh The mesh to generate infill for.
|
||||
*/
|
||||
Generator(const PrintObject &print_object);
|
||||
explicit Generator(const PrintObject &print_object);
|
||||
|
||||
/*!
|
||||
* Get a tree of paths generated for a certain layer of the mesh.
|
||||
|
@ -58,8 +57,12 @@ public:
|
|||
*/
|
||||
const Layer& getTreesForLayer(const size_t& layer_id) const;
|
||||
|
||||
std::vector<Polygons>& Overhangs() { return m_overhang_per_layer; }
|
||||
|
||||
float infilll_extrusion_width() const { return m_infill_extrusion_width; }
|
||||
|
||||
Generator(PrintObject* m_object, std::vector<Polygons>& contours, std::vector<Polygons>& overhangs, float density = 0.15);
|
||||
|
||||
protected:
|
||||
/*!
|
||||
* Calculate the overhangs above the infill areas that need to be supported
|
||||
|
@ -75,6 +78,7 @@ protected:
|
|||
* Calculate the tree structure of all layers.
|
||||
*/
|
||||
void generateTrees(const PrintObject &print_object);
|
||||
void generateTreesforSupport(std::vector<Polygons>& contours);
|
||||
|
||||
float m_infill_extrusion_width;
|
||||
|
||||
|
@ -124,6 +128,8 @@ protected:
|
|||
* This is generated by \ref generateTrees.
|
||||
*/
|
||||
std::vector<Layer> m_lightning_layers;
|
||||
|
||||
std::vector<BoundingBox> bboxs;
|
||||
};
|
||||
|
||||
} // namespace FillLightning
|
||||
|
|
|
@ -3,12 +3,16 @@
|
|||
|
||||
#include "Layer.hpp" //The class we're implementing.
|
||||
|
||||
#include <iterator> // advance
|
||||
|
||||
#include "DistanceField.hpp"
|
||||
#include "TreeNode.hpp"
|
||||
|
||||
#include "../../ClipperUtils.hpp"
|
||||
#include "../../Geometry.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/blocked_range2d.h>
|
||||
#include <mutex>
|
||||
|
||||
namespace Slic3r::FillLightning {
|
||||
|
||||
|
@ -23,10 +27,15 @@ Point GroundingLocation::p() const
|
|||
return tree_node ? tree_node->getLocation() : *boundary_location;
|
||||
}
|
||||
|
||||
void Layer::fillLocator(SparseNodeGrid &tree_node_locator)
|
||||
inline static Point to_grid_point(const Point &point, const BoundingBox &bbox)
|
||||
{
|
||||
std::function<void(NodeSPtr)> add_node_to_locator_func = [&tree_node_locator](NodeSPtr node) {
|
||||
tree_node_locator.insert(std::make_pair(Point(node->getLocation().x() / locator_cell_size, node->getLocation().y() / locator_cell_size), node));
|
||||
return (point - bbox.min) / locator_cell_size;
|
||||
}
|
||||
|
||||
void Layer::fillLocator(SparseNodeGrid &tree_node_locator, const BoundingBox& current_outlines_bbox)
|
||||
{
|
||||
std::function<void(NodeSPtr)> add_node_to_locator_func = [&tree_node_locator, ¤t_outlines_bbox](const NodeSPtr &node) {
|
||||
tree_node_locator.insert(std::make_pair(to_grid_point(node->getLocation(), current_outlines_bbox), node));
|
||||
};
|
||||
for (auto& tree : tree_roots)
|
||||
tree->visitNodes(add_node_to_locator_func);
|
||||
|
@ -36,38 +45,47 @@ void Layer::generateNewTrees
|
|||
(
|
||||
const Polygons& current_overhang,
|
||||
const Polygons& current_outlines,
|
||||
const BoundingBox& current_outlines_bbox,
|
||||
const EdgeGrid::Grid& outlines_locator,
|
||||
const coord_t supporting_radius,
|
||||
const coord_t wall_supporting_radius
|
||||
)
|
||||
{
|
||||
DistanceField distance_field(supporting_radius, current_outlines, current_overhang);
|
||||
DistanceField distance_field(supporting_radius, current_outlines, current_outlines_bbox, current_overhang);
|
||||
|
||||
SparseNodeGrid tree_node_locator;
|
||||
fillLocator(tree_node_locator);
|
||||
fillLocator(tree_node_locator, current_outlines_bbox);
|
||||
|
||||
// Until no more points need to be added to support all:
|
||||
// Determine next point from tree/outline areas via distance-field
|
||||
Point unsupported_location;
|
||||
while (distance_field.tryGetNextPoint(&unsupported_location)) {
|
||||
size_t unsupported_cell_idx = 0;
|
||||
Point unsupported_location;
|
||||
while (distance_field.tryGetNextPoint(&unsupported_location, &unsupported_cell_idx, unsupported_cell_idx)) {
|
||||
GroundingLocation grounding_loc = getBestGroundingLocation(
|
||||
unsupported_location, current_outlines, outlines_locator, supporting_radius, wall_supporting_radius, tree_node_locator);
|
||||
unsupported_location, current_outlines, current_outlines_bbox, outlines_locator, supporting_radius, wall_supporting_radius, tree_node_locator);
|
||||
|
||||
NodeSPtr new_parent;
|
||||
NodeSPtr new_child;
|
||||
this->attach(unsupported_location, grounding_loc, new_child, new_parent);
|
||||
tree_node_locator.insert(std::make_pair(Point(new_child->getLocation().x() / locator_cell_size, new_child->getLocation().y() / locator_cell_size), new_child));
|
||||
tree_node_locator.insert(std::make_pair(to_grid_point(new_child->getLocation(), current_outlines_bbox), new_child));
|
||||
if (new_parent)
|
||||
tree_node_locator.insert(std::make_pair(Point(new_parent->getLocation().x() / locator_cell_size, new_parent->getLocation().y() / locator_cell_size), new_parent));
|
||||
tree_node_locator.insert(std::make_pair(to_grid_point(new_parent->getLocation(), current_outlines_bbox), new_parent));
|
||||
// update distance field
|
||||
distance_field.update(grounding_loc.p(), unsupported_location);
|
||||
}
|
||||
|
||||
#ifdef LIGHTNING_TREE_NODE_DEBUG_OUTPUT
|
||||
{
|
||||
static int iRun = 0;
|
||||
export_to_svg(debug_out_path("FillLightning-TreeNodes-%d.svg", iRun++), current_outlines, this->tree_roots);
|
||||
}
|
||||
#endif /* LIGHTNING_TREE_NODE_DEBUG_OUTPUT */
|
||||
}
|
||||
|
||||
static bool polygonCollidesWithLineSegment(const Point from, const Point to, const EdgeGrid::Grid &loc_to_line)
|
||||
static bool polygonCollidesWithLineSegment(const Point &from, const Point &to, const EdgeGrid::Grid &loc_to_line)
|
||||
{
|
||||
struct Visitor {
|
||||
explicit Visitor(const EdgeGrid::Grid &grid) : grid(grid) {}
|
||||
explicit Visitor(const EdgeGrid::Grid &grid, const Line &line) : grid(grid), line(line) {}
|
||||
|
||||
bool operator()(coord_t iy, coord_t ix) {
|
||||
// Called with a row and colum of the grid cell, which is intersected by a line.
|
||||
|
@ -87,7 +105,7 @@ static bool polygonCollidesWithLineSegment(const Point from, const Point to, con
|
|||
const EdgeGrid::Grid& grid;
|
||||
Line line;
|
||||
bool intersect = false;
|
||||
} visitor(loc_to_line);
|
||||
} visitor(loc_to_line, {from, to});
|
||||
|
||||
loc_to_line.visit_cells_intersecting_line(from, to, visitor);
|
||||
return visitor.intersect;
|
||||
|
@ -97,6 +115,7 @@ GroundingLocation Layer::getBestGroundingLocation
|
|||
(
|
||||
const Point& unsupported_location,
|
||||
const Polygons& current_outlines,
|
||||
const BoundingBox& current_outlines_bbox,
|
||||
const EdgeGrid::Grid& outline_locator,
|
||||
const coord_t supporting_radius,
|
||||
const coord_t wall_supporting_radius,
|
||||
|
@ -112,9 +131,10 @@ GroundingLocation Layer::getBestGroundingLocation
|
|||
if (contour.size() > 2) {
|
||||
Point prev = contour.points.back();
|
||||
for (const Point &p2 : contour.points) {
|
||||
if (double d = Line::distance_to_squared(unsupported_location, prev, p2); d < d2) {
|
||||
Point closest_point;
|
||||
if (double d = line_alg::distance_to_squared(Line{prev, p2}, unsupported_location, &closest_point); d < d2) {
|
||||
d2 = d;
|
||||
node_location = Geometry::foot_pt({ prev, p2 }, unsupported_location).cast<coord_t>();
|
||||
node_location = closest_point;
|
||||
}
|
||||
prev = p2;
|
||||
}
|
||||
|
@ -123,30 +143,52 @@ GroundingLocation Layer::getBestGroundingLocation
|
|||
|
||||
const auto within_dist = coord_t((node_location - unsupported_location).cast<double>().norm());
|
||||
|
||||
NodeSPtr sub_tree{ nullptr };
|
||||
coord_t current_dist = getWeightedDistance(node_location, unsupported_location);
|
||||
NodeSPtr sub_tree{nullptr};
|
||||
coord_t current_dist = getWeightedDistance(node_location, unsupported_location);
|
||||
if (current_dist >= wall_supporting_radius) { // Only reconnect tree roots to other trees if they are not already close to the outlines.
|
||||
const coord_t search_radius = std::min(current_dist, within_dist);
|
||||
BoundingBox region(unsupported_location - Point(search_radius, search_radius), unsupported_location + Point(search_radius + locator_cell_size, search_radius + locator_cell_size));
|
||||
region.min /= locator_cell_size;
|
||||
region.max /= locator_cell_size;
|
||||
Point grid_addr;
|
||||
for (grid_addr.y() = region.min.y(); grid_addr.y() < region.max.y(); ++ grid_addr.y())
|
||||
for (grid_addr.x() = region.min.x(); grid_addr.x() < region.max.x(); ++ grid_addr.x()) {
|
||||
auto it_range = tree_node_locator.equal_range(grid_addr);
|
||||
for (auto it = it_range.first; it != it_range.second; ++ it) {
|
||||
auto candidate_sub_tree = it->second.lock();
|
||||
if ((candidate_sub_tree && candidate_sub_tree != exclude_tree) &&
|
||||
!(exclude_tree && exclude_tree->hasOffspring(candidate_sub_tree)) &&
|
||||
!polygonCollidesWithLineSegment(unsupported_location, candidate_sub_tree->getLocation(), outline_locator)) {
|
||||
const coord_t candidate_dist = candidate_sub_tree->getWeightedDistance(unsupported_location, supporting_radius);
|
||||
if (candidate_dist < current_dist) {
|
||||
current_dist = candidate_dist;
|
||||
sub_tree = candidate_sub_tree;
|
||||
region.min = to_grid_point(region.min, current_outlines_bbox);
|
||||
region.max = to_grid_point(region.max, current_outlines_bbox);
|
||||
|
||||
Point current_dist_grid_addr{std::numeric_limits<coord_t>::lowest(), std::numeric_limits<coord_t>::lowest()};
|
||||
std::mutex current_dist_mutex;
|
||||
tbb::parallel_for(tbb::blocked_range2d<coord_t>(region.min.y(), region.max.y(), region.min.x(), region.max.x()), [¤t_dist, current_dist_copy = current_dist, ¤t_dist_mutex, &sub_tree, ¤t_dist_grid_addr, &exclude_tree = std::as_const(exclude_tree), &outline_locator = std::as_const(outline_locator), &supporting_radius = std::as_const(supporting_radius), &tree_node_locator = std::as_const(tree_node_locator), &unsupported_location = std::as_const(unsupported_location)](const tbb::blocked_range2d<coord_t> &range) -> void {
|
||||
for (coord_t grid_addr_y = range.rows().begin(); grid_addr_y < range.rows().end(); ++grid_addr_y)
|
||||
for (coord_t grid_addr_x = range.cols().begin(); grid_addr_x < range.cols().end(); ++grid_addr_x) {
|
||||
const Point local_grid_addr{grid_addr_x, grid_addr_y};
|
||||
NodeSPtr local_sub_tree{nullptr};
|
||||
coord_t local_current_dist = current_dist_copy;
|
||||
const auto it_range = tree_node_locator.equal_range(local_grid_addr);
|
||||
for (auto it = it_range.first; it != it_range.second; ++it) {
|
||||
const NodeSPtr candidate_sub_tree = it->second.lock();
|
||||
if ((candidate_sub_tree && candidate_sub_tree != exclude_tree) &&
|
||||
!(exclude_tree && exclude_tree->hasOffspring(candidate_sub_tree)) &&
|
||||
!polygonCollidesWithLineSegment(unsupported_location, candidate_sub_tree->getLocation(), outline_locator)) {
|
||||
if (const coord_t candidate_dist = candidate_sub_tree->getWeightedDistance(unsupported_location, supporting_radius); candidate_dist < local_current_dist) {
|
||||
local_current_dist = candidate_dist;
|
||||
local_sub_tree = candidate_sub_tree;
|
||||
}
|
||||
}
|
||||
}
|
||||
// To always get the same result in a parallel version as in a non-parallel version,
|
||||
// we need to preserve that for the same current_dist, we select the same sub_tree
|
||||
// as in the non-parallel version. For this purpose, inside the variable
|
||||
// current_dist_grid_addr is stored from with 2D grid position assigned sub_tree comes.
|
||||
// And when there are two sub_tree with the same current_dist, one which will be found
|
||||
// the first in the non-parallel version is selected.
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(current_dist_mutex);
|
||||
if (local_current_dist < current_dist ||
|
||||
(local_current_dist == current_dist && (grid_addr_y < current_dist_grid_addr.y() ||
|
||||
(grid_addr_y == current_dist_grid_addr.y() && grid_addr_x < current_dist_grid_addr.x())))) {
|
||||
current_dist = local_current_dist;
|
||||
sub_tree = local_sub_tree;
|
||||
current_dist_grid_addr = local_grid_addr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}); // end of parallel_for
|
||||
}
|
||||
|
||||
return ! sub_tree ?
|
||||
|
@ -176,6 +218,7 @@ void Layer::reconnectRoots
|
|||
(
|
||||
std::vector<NodeSPtr>& to_be_reconnected_tree_roots,
|
||||
const Polygons& current_outlines,
|
||||
const BoundingBox& current_outlines_bbox,
|
||||
const EdgeGrid::Grid& outline_locator,
|
||||
const coord_t supporting_radius,
|
||||
const coord_t wall_supporting_radius
|
||||
|
@ -184,10 +227,10 @@ void Layer::reconnectRoots
|
|||
constexpr coord_t tree_connecting_ignore_offset = 100;
|
||||
|
||||
SparseNodeGrid tree_node_locator;
|
||||
fillLocator(tree_node_locator);
|
||||
fillLocator(tree_node_locator, current_outlines_bbox);
|
||||
|
||||
const coord_t within_max_dist = outline_locator.resolution() * 2;
|
||||
for (auto root_ptr : to_be_reconnected_tree_roots)
|
||||
for (const auto &root_ptr : to_be_reconnected_tree_roots)
|
||||
{
|
||||
auto old_root_it = std::find(tree_roots.begin(), tree_roots.end(), root_ptr);
|
||||
|
||||
|
@ -203,7 +246,7 @@ void Layer::reconnectRoots
|
|||
root_ptr->addChild(new_root);
|
||||
new_root->reroot();
|
||||
|
||||
tree_node_locator.insert(std::make_pair(Point(new_root->getLocation().x() / locator_cell_size, new_root->getLocation().y() / locator_cell_size), new_root));
|
||||
tree_node_locator.insert(std::make_pair(to_grid_point(new_root->getLocation(), current_outlines_bbox), new_root));
|
||||
|
||||
*old_root_it = std::move(new_root); // replace old root with new root
|
||||
continue;
|
||||
|
@ -217,6 +260,7 @@ void Layer::reconnectRoots
|
|||
(
|
||||
root_ptr->getLocation(),
|
||||
current_outlines,
|
||||
current_outlines_bbox,
|
||||
outline_locator,
|
||||
supporting_radius,
|
||||
tree_connecting_ignore_width,
|
||||
|
@ -233,7 +277,7 @@ void Layer::reconnectRoots
|
|||
attach_ptr->reroot();
|
||||
|
||||
new_root->addChild(attach_ptr);
|
||||
tree_node_locator.insert(std::make_pair(new_root->getLocation(), new_root));
|
||||
tree_node_locator.insert(std::make_pair(to_grid_point(new_root->getLocation(), current_outlines_bbox), new_root));
|
||||
|
||||
*old_root_it = std::move(new_root); // replace old root with new root
|
||||
}
|
||||
|
@ -256,15 +300,26 @@ void Layer::reconnectRoots
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Implementation assumes moving inside, but moving outside should just as well be possible.
|
||||
#if 0
|
||||
/*!
|
||||
* Moves the point \p from onto the nearest polygon or leaves the point as-is, when the comb boundary is not within the root of \p max_dist2 distance.
|
||||
* Given a \p distance more than zero, the point will end up inside, and conversely outside.
|
||||
* When the point is already in/outside by more than \p distance, \p from is unaltered, but the polygon is returned.
|
||||
* When the point is in/outside by less than \p distance, \p from is moved to the correct place.
|
||||
* Implementation assumes moving inside, but moving outside should just as well be possible.
|
||||
*
|
||||
* \param polygons The polygons onto which to move the point
|
||||
* \param from[in,out] The point to move.
|
||||
* \param distance The distance by which to move the point.
|
||||
* \param max_dist2 The squared maximal allowed distance from the point to the nearest polygon.
|
||||
* \return The index to the polygon onto which we have moved the point.
|
||||
*/
|
||||
static unsigned int moveInside(const Polygons& polygons, Point& from, int distance, int64_t maxDist2)
|
||||
{
|
||||
Point ret = from;
|
||||
int64_t bestDist2 = std::numeric_limits<int64_t>::max();
|
||||
unsigned int bestPoly = static_cast<unsigned int>(-1);
|
||||
bool is_already_on_correct_side_of_boundary = false; // whether [from] is already on the right side of the boundary
|
||||
Point ret = from;
|
||||
int64_t bestDist2 = std::numeric_limits<int64_t>::max();
|
||||
auto bestPoly = static_cast<unsigned int>(-1);
|
||||
bool is_already_on_correct_side_of_boundary = false; // whether [from] is already on the right side of the boundary
|
||||
for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++)
|
||||
{
|
||||
const Polygon &poly = polygons[poly_idx];
|
||||
|
@ -333,7 +388,7 @@ static unsigned int moveInside(const Polygons& polygons, Point& from, int distan
|
|||
else
|
||||
{ // x is projected to a point properly on the line segment (not onto a vertex). The case which looks like | .
|
||||
projected_p_beyond_prev_segment = false;
|
||||
Point x = a + ab * dot_prod / ab_length2;
|
||||
Point x = (a.cast<int64_t>() + ab.cast<int64_t>() * dot_prod / ab_length2).cast<coord_t>();
|
||||
|
||||
int64_t dist2 = (p - x).cast<int64_t>().squaredNorm();
|
||||
if (dist2 < bestDist2)
|
||||
|
@ -373,38 +428,18 @@ static unsigned int moveInside(const Polygons& polygons, Point& from, int distan
|
|||
}
|
||||
return static_cast<unsigned int>(-1);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Returns 'added someting'.
|
||||
Polylines Layer::convertToLines(const Polygons& limit_to_outline, const coord_t line_width) const
|
||||
Polylines Layer::convertToLines(const Polygons& limit_to_outline, const coord_t line_overlap) const
|
||||
{
|
||||
if (tree_roots.empty())
|
||||
return {};
|
||||
|
||||
Polygons result_lines;
|
||||
for (const auto& tree : tree_roots) {
|
||||
// If even the furthest location in the tree is inside the polygon, the entire tree must be inside of the polygon.
|
||||
// (Don't take the root as that may be on the edge and cause rounding errors to register as 'outside'.)
|
||||
constexpr coord_t epsilon = 5;
|
||||
Point should_be_inside = tree->getLocation();
|
||||
moveInside(limit_to_outline, should_be_inside, epsilon, epsilon * epsilon);
|
||||
if (inside(limit_to_outline, should_be_inside))
|
||||
tree->convertToPolylines(result_lines, line_width);
|
||||
}
|
||||
Polylines result_lines;
|
||||
for (const auto &tree : tree_roots)
|
||||
tree->convertToPolylines(result_lines, line_overlap);
|
||||
|
||||
// TODO: allow for polylines!
|
||||
Polylines split_lines;
|
||||
for (Polygon &line : result_lines) {
|
||||
if (line.size() <= 1)
|
||||
continue;
|
||||
Point last = line[0];
|
||||
for (size_t point_idx = 1; point_idx < line.size(); point_idx++) {
|
||||
Point here = line[point_idx];
|
||||
split_lines.push_back({ last, here });
|
||||
last = here;
|
||||
}
|
||||
}
|
||||
|
||||
return split_lines;
|
||||
return intersection_pl(result_lines, limit_to_outline);
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Lightning
|
||||
|
|
|
@ -41,9 +41,10 @@ public:
|
|||
(
|
||||
const Polygons& current_overhang,
|
||||
const Polygons& current_outlines,
|
||||
const BoundingBox& current_outlines_bbox,
|
||||
const EdgeGrid::Grid& outline_locator,
|
||||
const coord_t supporting_radius,
|
||||
const coord_t wall_supporting_radius
|
||||
coord_t supporting_radius,
|
||||
coord_t wall_supporting_radius
|
||||
);
|
||||
|
||||
/*! Determine & connect to connection point in tree/outline.
|
||||
|
@ -53,9 +54,10 @@ public:
|
|||
(
|
||||
const Point& unsupported_location,
|
||||
const Polygons& current_outlines,
|
||||
const BoundingBox& current_outlines_bbox,
|
||||
const EdgeGrid::Grid& outline_locator,
|
||||
const coord_t supporting_radius,
|
||||
const coord_t wall_supporting_radius,
|
||||
coord_t supporting_radius,
|
||||
coord_t wall_supporting_radius,
|
||||
const SparseNodeGrid& tree_node_locator,
|
||||
const NodeSPtr& exclude_tree = nullptr
|
||||
);
|
||||
|
@ -71,16 +73,17 @@ public:
|
|||
(
|
||||
std::vector<NodeSPtr>& to_be_reconnected_tree_roots,
|
||||
const Polygons& current_outlines,
|
||||
const BoundingBox& current_outlines_bbox,
|
||||
const EdgeGrid::Grid& outline_locator,
|
||||
const coord_t supporting_radius,
|
||||
const coord_t wall_supporting_radius
|
||||
coord_t supporting_radius,
|
||||
coord_t wall_supporting_radius
|
||||
);
|
||||
|
||||
Polylines convertToLines(const Polygons& limit_to_outline, const coord_t line_width) const;
|
||||
Polylines convertToLines(const Polygons& limit_to_outline, coord_t line_overlap) const;
|
||||
|
||||
coord_t getWeightedDistance(const Point& boundary_loc, const Point& unsupported_location);
|
||||
|
||||
void fillLocator(SparseNodeGrid& tree_node_locator);
|
||||
void fillLocator(SparseNodeGrid& tree_node_locator, const BoundingBox& current_outlines_bbox);
|
||||
};
|
||||
|
||||
} // namespace Slic3r::FillLightning
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
#include "TreeNode.hpp"
|
||||
|
||||
#include "../../Geometry.hpp"
|
||||
#include "../../ClipperUtils.hpp"
|
||||
|
||||
namespace Slic3r::FillLightning {
|
||||
|
||||
|
@ -107,7 +106,7 @@ NodeSPtr Node::deepCopy() const
|
|||
return local_root;
|
||||
}
|
||||
|
||||
void Node::reroot(NodeSPtr new_parent /*= nullptr*/)
|
||||
void Node::reroot(const NodeSPtr &new_parent)
|
||||
{
|
||||
if (! m_is_root) {
|
||||
auto old_parent = m_parent.lock();
|
||||
|
@ -142,7 +141,7 @@ NodeSPtr Node::closestNode(const Point& loc)
|
|||
return result;
|
||||
}
|
||||
|
||||
bool inside(const Polygons &polygons, const Point p)
|
||||
bool inside(const Polygons &polygons, const Point &p)
|
||||
{
|
||||
int poly_count_inside = 0;
|
||||
for (const Polygon &poly : polygons) {
|
||||
|
@ -181,7 +180,11 @@ bool lineSegmentPolygonsIntersection(const Point& a, const Point& b, const EdgeG
|
|||
} visitor { outline_locator, a.cast<double>(), b.cast<double>() };
|
||||
|
||||
outline_locator.visit_cells_intersecting_line(a, b, visitor);
|
||||
return visitor.d2min < within_max_dist * within_max_dist;
|
||||
if (visitor.d2min < double(within_max_dist) * double(within_max_dist)) {
|
||||
result = Point(visitor.intersection_pt);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Node::realign(const Polygons& outlines, const EdgeGrid::Grid& outline_locator, std::vector<NodeSPtr>& rerooted_parts)
|
||||
|
@ -226,14 +229,14 @@ bool Node::realign(const Polygons& outlines, const EdgeGrid::Grid& outline_locat
|
|||
|
||||
void Node::straighten(const coord_t magnitude, const coord_t max_remove_colinear_dist)
|
||||
{
|
||||
straighten(magnitude, m_p, 0, max_remove_colinear_dist * max_remove_colinear_dist);
|
||||
straighten(magnitude, m_p, 0, int64_t(max_remove_colinear_dist) * int64_t(max_remove_colinear_dist));
|
||||
}
|
||||
|
||||
Node::RectilinearJunction Node::straighten(
|
||||
const coord_t magnitude,
|
||||
const Point& junction_above,
|
||||
const coord_t accumulated_dist,
|
||||
const coord_t max_remove_colinear_dist2)
|
||||
const int64_t max_remove_colinear_dist2)
|
||||
{
|
||||
constexpr coord_t junction_magnitude_factor_numerator = 3;
|
||||
constexpr coord_t junction_magnitude_factor_denominator = 4;
|
||||
|
@ -245,13 +248,13 @@ Node::RectilinearJunction Node::straighten(
|
|||
auto child_dist = coord_t((m_p - child_p->m_p).cast<double>().norm());
|
||||
RectilinearJunction junction_below = child_p->straighten(magnitude, junction_above, accumulated_dist + child_dist, max_remove_colinear_dist2);
|
||||
coord_t total_dist_to_junction_below = junction_below.total_recti_dist;
|
||||
Point a = junction_above;
|
||||
Point b = junction_below.junction_loc;
|
||||
const Point& a = junction_above;
|
||||
Point b = junction_below.junction_loc;
|
||||
if (a != b) // should always be true!
|
||||
{
|
||||
Point ab = b - a;
|
||||
Point destination = a + ab * accumulated_dist / std::max(coord_t(1), total_dist_to_junction_below);
|
||||
if ((destination - m_p).cast<double>().squaredNorm() <= magnitude * magnitude)
|
||||
Point destination = (a.cast<int64_t>() + ab.cast<int64_t>() * int64_t(accumulated_dist) / std::max(int64_t(1), int64_t(total_dist_to_junction_below))).cast<coord_t>();
|
||||
if ((destination - m_p).cast<int64_t>().squaredNorm() <= int64_t(magnitude) * int64_t(magnitude))
|
||||
m_p = destination;
|
||||
else
|
||||
m_p += ((destination - m_p).cast<double>().normalized() * magnitude).cast<coord_t>();
|
||||
|
@ -262,7 +265,7 @@ Node::RectilinearJunction Node::straighten(
|
|||
child_p = m_children.front(); //recursive call to straighten might have removed the child
|
||||
const NodeSPtr& parent_node = m_parent.lock();
|
||||
if (parent_node &&
|
||||
(child_p->m_p - parent_node->m_p).cast<double>().squaredNorm() < max_remove_colinear_dist2 &&
|
||||
(child_p->m_p - parent_node->m_p).cast<int64_t>().squaredNorm() < max_remove_colinear_dist2 &&
|
||||
Line::distance_to_squared(m_p, parent_node->m_p, child_p->m_p) < close_enough * close_enough) {
|
||||
child_p->m_parent = m_parent;
|
||||
for (auto& sibling : parent_node->m_children)
|
||||
|
@ -344,16 +347,16 @@ coord_t Node::prune(const coord_t& pruning_distance)
|
|||
return max_distance_pruned;
|
||||
}
|
||||
|
||||
void Node::convertToPolylines(Polygons& output, const coord_t line_width) const
|
||||
void Node::convertToPolylines(Polylines &output, const coord_t line_overlap) const
|
||||
{
|
||||
Polygons result;
|
||||
output.emplace_back();
|
||||
Polylines result;
|
||||
result.emplace_back();
|
||||
convertToPolylines(0, result);
|
||||
removeJunctionOverlap(result, line_width);
|
||||
removeJunctionOverlap(result, line_overlap);
|
||||
append(output, std::move(result));
|
||||
}
|
||||
|
||||
void Node::convertToPolylines(size_t long_line_idx, Polygons& output) const
|
||||
void Node::convertToPolylines(size_t long_line_idx, Polylines &output) const
|
||||
{
|
||||
if (m_children.empty()) {
|
||||
output[long_line_idx].points.push_back(m_p);
|
||||
|
@ -373,11 +376,12 @@ void Node::convertToPolylines(size_t long_line_idx, Polygons& output) const
|
|||
}
|
||||
}
|
||||
|
||||
void Node::removeJunctionOverlap(Polygons& result_lines, const coord_t line_width) const
|
||||
void Node::removeJunctionOverlap(Polylines &result_lines, const coord_t line_overlap) const
|
||||
{
|
||||
const coord_t reduction = line_width / 2; // TODO make configurable?
|
||||
for (auto poly_it = result_lines.begin(); poly_it != result_lines.end(); ) {
|
||||
Polygon &polyline = *poly_it;
|
||||
const coord_t reduction = line_overlap;
|
||||
size_t res_line_idx = 0;
|
||||
while (res_line_idx < result_lines.size()) {
|
||||
Polyline &polyline = result_lines[res_line_idx];
|
||||
if (polyline.size() <= 1) {
|
||||
polyline = std::move(result_lines.back());
|
||||
result_lines.pop_back();
|
||||
|
@ -386,8 +390,8 @@ void Node::removeJunctionOverlap(Polygons& result_lines, const coord_t line_widt
|
|||
|
||||
coord_t to_be_reduced = reduction;
|
||||
Point a = polyline.back();
|
||||
for (int point_idx = polyline.size() - 2; point_idx >= 0; point_idx--) {
|
||||
const Point b = polyline[point_idx];
|
||||
for (int point_idx = int(polyline.size()) - 2; point_idx >= 0; point_idx--) {
|
||||
const Point b = polyline.points[point_idx];
|
||||
const Point ab = b - a;
|
||||
const auto ab_len = coord_t(ab.cast<double>().norm());
|
||||
if (ab_len >= to_be_reduced) {
|
||||
|
@ -404,8 +408,33 @@ void Node::removeJunctionOverlap(Polygons& result_lines, const coord_t line_widt
|
|||
polyline = std::move(result_lines.back());
|
||||
result_lines.pop_back();
|
||||
} else
|
||||
++ poly_it;
|
||||
++ res_line_idx;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef LIGHTNING_TREE_NODE_DEBUG_OUTPUT
|
||||
void export_to_svg(const NodeSPtr &root_node, SVG &svg)
|
||||
{
|
||||
for (const NodeSPtr &children : root_node->m_children) {
|
||||
svg.draw(Line(root_node->getLocation(), children->getLocation()), "red");
|
||||
export_to_svg(children, svg);
|
||||
}
|
||||
}
|
||||
|
||||
void export_to_svg(const std::string &path, const Polygons &contour, const std::vector<NodeSPtr> &root_nodes) {
|
||||
BoundingBox bbox = get_extents(contour);
|
||||
|
||||
bbox.offset(SCALED_EPSILON);
|
||||
SVG svg(path, bbox);
|
||||
svg.draw_outline(contour, "blue");
|
||||
|
||||
for (const NodeSPtr &root_node: root_nodes) {
|
||||
for (const NodeSPtr &children: root_node->m_children) {
|
||||
svg.draw(Line(root_node->getLocation(), children->getLocation()), "red");
|
||||
export_to_svg(children, svg);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* LIGHTNING_TREE_NODE_DEBUG_OUTPUT */
|
||||
|
||||
} // namespace Slic3r::FillLightning
|
||||
|
|
|
@ -11,6 +11,9 @@
|
|||
|
||||
#include "../../EdgeGrid.hpp"
|
||||
#include "../../Polygon.hpp"
|
||||
#include "SVG.hpp"
|
||||
|
||||
//#define LIGHTNING_TREE_NODE_DEBUG_OUTPUT
|
||||
|
||||
namespace Slic3r::FillLightning
|
||||
{
|
||||
|
@ -43,7 +46,7 @@ public:
|
|||
{
|
||||
struct EnableMakeShared : public Node
|
||||
{
|
||||
EnableMakeShared(Arg&&...arg) : Node(std::forward<Arg>(arg)...) {}
|
||||
explicit EnableMakeShared(Arg&&...arg) : Node(std::forward<Arg>(arg)...) {}
|
||||
};
|
||||
return std::make_shared<EnableMakeShared>(std::forward<Arg>(arg)...);
|
||||
}
|
||||
|
@ -99,9 +102,9 @@ public:
|
|||
std::vector<NodeSPtr>& next_trees,
|
||||
const Polygons& next_outlines,
|
||||
const EdgeGrid::Grid& outline_locator,
|
||||
const coord_t prune_distance,
|
||||
const coord_t smooth_magnitude,
|
||||
const coord_t max_remove_colinear_dist
|
||||
coord_t prune_distance,
|
||||
coord_t smooth_magnitude,
|
||||
coord_t max_remove_colinear_dist
|
||||
) const;
|
||||
|
||||
/*!
|
||||
|
@ -156,7 +159,7 @@ public:
|
|||
* This is then recursively bubbled up until it reaches the (former) root, which then will become a leaf.
|
||||
* \param new_parent The (new) parent-node of the root, useful for recursing or immediately attaching the node to another tree.
|
||||
*/
|
||||
void reroot(NodeSPtr new_parent = nullptr);
|
||||
void reroot(const NodeSPtr &new_parent = nullptr);
|
||||
|
||||
/*!
|
||||
* Retrieves the closest node to the specified location.
|
||||
|
@ -176,16 +179,16 @@ public:
|
|||
*/
|
||||
bool hasOffspring(const NodeSPtr& to_be_checked) const;
|
||||
|
||||
protected:
|
||||
Node() = delete; // Don't allow empty contruction
|
||||
|
||||
protected:
|
||||
/*!
|
||||
* Construct a new node, either for insertion in a tree or as root.
|
||||
* \param p The physical location in the 2D layer that this node represents.
|
||||
* Connecting other nodes to this node indicates that a line segment should
|
||||
* be drawn between those two physical positions.
|
||||
*/
|
||||
Node(const Point& p, const std::optional<Point>& last_grounding_location = std::nullopt);
|
||||
explicit Node(const Point& p, const std::optional<Point>& last_grounding_location = std::nullopt);
|
||||
|
||||
/*!
|
||||
* Copy this node and its entire sub-tree.
|
||||
|
@ -211,7 +214,7 @@ protected:
|
|||
* \param magnitude The maximum allowed distance to move the node.
|
||||
* \param max_remove_colinear_dist Maximum distance of the (compound) line-segment from which a co-linear point may be removed.
|
||||
*/
|
||||
void straighten(const coord_t magnitude, const coord_t max_remove_colinear_dist);
|
||||
void straighten(coord_t magnitude, coord_t max_remove_colinear_dist);
|
||||
|
||||
/*! Recursive part of \ref straighten(.)
|
||||
* \param junction_above The last seen junction with multiple children above
|
||||
|
@ -219,7 +222,7 @@ protected:
|
|||
* \param max_remove_colinear_dist2 Maximum distance _squared_ of the (compound) line-segment from which a co-linear point may be removed.
|
||||
* \return the total distance along the tree from the last junction above to the first next junction below and the location of the next junction below
|
||||
*/
|
||||
RectilinearJunction straighten(const coord_t magnitude, const Point& junction_above, const coord_t accumulated_dist, const coord_t max_remove_colinear_dist2);
|
||||
RectilinearJunction straighten(coord_t magnitude, const Point& junction_above, coord_t accumulated_dist, int64_t max_remove_colinear_dist2);
|
||||
|
||||
/*! Prune the tree from the extremeties (leaf-nodes) until the pruning distance is reached.
|
||||
* \return The distance that has been pruned. If less than \p distance, then the whole tree was puned away.
|
||||
|
@ -236,7 +239,7 @@ public:
|
|||
*
|
||||
* \param output all branches in this tree connected into polylines
|
||||
*/
|
||||
void convertToPolylines(Polygons& output, const coord_t line_width) const;
|
||||
void convertToPolylines(Polylines &output, coord_t line_overlap) const;
|
||||
|
||||
/*! If this was ever a direct child of the root, it'll have a previous grounding location.
|
||||
*
|
||||
|
@ -244,6 +247,8 @@ public:
|
|||
*/
|
||||
const std::optional<Point>& getLastGroundingLocation() const { return m_last_grounding_location; }
|
||||
|
||||
void draw_tree(SVG& svg) { for (auto& child : m_children) { svg.draw(Line(m_p, child->getLocation()), "yellow"); child->draw_tree(svg); } }
|
||||
|
||||
protected:
|
||||
/*!
|
||||
* Convert the tree into polylines
|
||||
|
@ -255,9 +260,9 @@ protected:
|
|||
* \param long_line a reference to a polyline in \p output which to continue building on in the recursion
|
||||
* \param output all branches in this tree connected into polylines
|
||||
*/
|
||||
void convertToPolylines(size_t long_line_idx, Polygons& output) const;
|
||||
void convertToPolylines(size_t long_line_idx, Polylines &output) const;
|
||||
|
||||
void removeJunctionOverlap(Polygons& polylines, const coord_t line_width) const;
|
||||
void removeJunctionOverlap(Polylines &polylines, coord_t line_overlap) const;
|
||||
|
||||
bool m_is_root;
|
||||
Point m_p;
|
||||
|
@ -265,10 +270,40 @@ protected:
|
|||
std::vector<NodeSPtr> m_children;
|
||||
|
||||
std::optional<Point> m_last_grounding_location; //<! The last known grounding location, see 'getLastGroundingLocation()'.
|
||||
|
||||
friend BoundingBox get_extents(const NodeSPtr &root_node);
|
||||
friend BoundingBox get_extents(const std::vector<NodeSPtr> &tree_roots);
|
||||
|
||||
#ifdef LIGHTNING_TREE_NODE_DEBUG_OUTPUT
|
||||
friend void export_to_svg(const NodeSPtr &root_node, Slic3r::SVG &svg);
|
||||
friend void export_to_svg(const std::string &path, const Polygons &contour, const std::vector<NodeSPtr> &root_nodes);
|
||||
#endif /* LIGHTNING_TREE_NODE_DEBUG_OUTPUT */
|
||||
};
|
||||
|
||||
bool inside(const Polygons &polygons, const Point p);
|
||||
bool lineSegmentPolygonsIntersection(const Point& a, const Point& b, const EdgeGrid::Grid& outline_locator, Point& result, const coord_t within_max_dist);
|
||||
bool inside(const Polygons &polygons, const Point &p);
|
||||
bool lineSegmentPolygonsIntersection(const Point& a, const Point& b, const EdgeGrid::Grid& outline_locator, Point& result, coord_t within_max_dist);
|
||||
|
||||
inline BoundingBox get_extents(const NodeSPtr &root_node)
|
||||
{
|
||||
BoundingBox bbox;
|
||||
for (const NodeSPtr &children : root_node->m_children)
|
||||
bbox.merge(get_extents(children));
|
||||
bbox.merge(root_node->getLocation());
|
||||
return bbox;
|
||||
}
|
||||
|
||||
inline BoundingBox get_extents(const std::vector<NodeSPtr> &tree_roots)
|
||||
{
|
||||
BoundingBox bbox;
|
||||
for (const NodeSPtr &root_node : tree_roots)
|
||||
bbox.merge(get_extents(root_node));
|
||||
return bbox;
|
||||
}
|
||||
|
||||
#ifdef LIGHTNING_TREE_NODE_DEBUG_OUTPUT
|
||||
void export_to_svg(const NodeSPtr &root_node, SVG &svg);
|
||||
void export_to_svg(const std::string &path, const Polygons &contour, const std::vector<NodeSPtr> &root_nodes);
|
||||
#endif /* LIGHTNING_TREE_NODE_DEBUG_OUTPUT */
|
||||
|
||||
} // namespace Slic3r::FillLightning
|
||||
|
||||
|
|
|
@ -3189,7 +3189,7 @@ bool _3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_archiv
|
|||
//BBS
|
||||
std::string gcode = //code.type == CustomGCode::ColorChange ? config->opt_string("color_change_gcode") :
|
||||
code.type == CustomGCode::PausePrint ? config->opt_string("machine_pause_gcode") :
|
||||
//code.type == CustomGCode::Template ? config->opt_string("template_custom_gcode") :
|
||||
code.type == CustomGCode::Template ? config->opt_string("template_custom_gcode") :
|
||||
code.type == CustomGCode::ToolChange ? "tool_change" : code.extra;
|
||||
code_tree.put("<xmlattr>.gcode" , gcode );
|
||||
}
|
||||
|
|
|
@ -1336,7 +1336,7 @@ bool load_amf(const char *path, DynamicPrintConfig *config, ConfigSubstitutionCo
|
|||
//BBS
|
||||
std::string gcode = //code.type == CustomGCode::ColorChange ? config->opt_string("color_change_gcode") :
|
||||
code.type == CustomGCode::PausePrint ? config->opt_string("machine_pause_gcode") :
|
||||
//code.type == CustomGCode::Template ? config->opt_string("template_custom_gcode") :
|
||||
code.type == CustomGCode::Template ? config->opt_string("template_custom_gcode") :
|
||||
code.type == CustomGCode::ToolChange ? "tool_change" : code.extra;
|
||||
code_tree.put("<xmlattr>.gcode" , gcode );
|
||||
}
|
||||
|
|
|
@ -175,6 +175,8 @@ static constexpr const char* BUILD_TAG = "build";
|
|||
static constexpr const char* ITEM_TAG = "item";
|
||||
static constexpr const char* METADATA_TAG = "metadata";
|
||||
static constexpr const char* FILAMENT_TAG = "filament";
|
||||
static constexpr const char* SLICE_WARNING_TAG = "warning";
|
||||
static constexpr const char* WARNING_MSG_TAG = "msg";
|
||||
static constexpr const char *FILAMENT_ID_TAG = "id";
|
||||
static constexpr const char* FILAMENT_TYPE_TAG = "type";
|
||||
static constexpr const char *FILAMENT_COLOR_TAG = "color";
|
||||
|
@ -454,12 +456,28 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
for (auto it = ps.volumes_per_extruder.begin(); it != ps.volumes_per_extruder.end(); it++) {
|
||||
double volume = it->second;
|
||||
auto [used_filament_m, used_filament_g] = get_used_filament_from_volume(volume, it->first);
|
||||
|
||||
FilamentInfo info;
|
||||
info.id = it->first;
|
||||
info.used_m = used_filament_m;
|
||||
info.used_g = used_filament_g;
|
||||
info.id = it->first;
|
||||
if (ps.flush_per_filament.find(it->first) != ps.flush_per_filament.end()) {
|
||||
volume = ps.flush_per_filament.at(it->first);
|
||||
auto [flushed_filament_m, flushed_filament_g] = get_used_filament_from_volume(volume, it->first);
|
||||
info.used_m = used_filament_m + flushed_filament_m;
|
||||
info.used_g = used_filament_g + flushed_filament_g;
|
||||
} else {
|
||||
info.used_m = used_filament_m;
|
||||
info.used_g = used_filament_g;
|
||||
}
|
||||
slice_filaments_info.push_back(info);
|
||||
}
|
||||
|
||||
/* only for test
|
||||
GCodeProcessorResult::SliceWarning sw;
|
||||
sw.msg = BED_TEMP_TOO_HIGH_THAN_FILAMENT;
|
||||
sw.level = 1;
|
||||
result->warnings.push_back(sw);
|
||||
*/
|
||||
warnings = result->warnings;
|
||||
}
|
||||
|
||||
|
||||
|
@ -843,6 +861,9 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
bool _handle_start_config_filament(const char** attributes, unsigned int num_attributes);
|
||||
bool _handle_end_config_filament();
|
||||
|
||||
bool _handle_start_config_warning(const char** attributes, unsigned int num_attributes);
|
||||
bool _handle_end_config_warning();
|
||||
|
||||
//BBS: add plater config parse functions
|
||||
bool _handle_start_config_plater(const char** attributes, unsigned int num_attributes);
|
||||
bool _handle_end_config_plater();
|
||||
|
@ -1510,6 +1531,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
plate_data_list[it->first-1]->gcode_weight = it->second->gcode_weight;
|
||||
plate_data_list[it->first-1]->toolpath_outside = it->second->toolpath_outside;
|
||||
plate_data_list[it->first-1]->slice_filaments_info = it->second->slice_filaments_info;
|
||||
plate_data_list[it->first-1]->warnings = it->second->warnings;
|
||||
plate_data_list[it->first-1]->thumbnail_file = (m_load_restore || it->second->thumbnail_file.empty()) ? it->second->thumbnail_file : m_backup_path + "/" + it->second->thumbnail_file;
|
||||
plate_data_list[it->first-1]->pattern_file = (m_load_restore || it->second->pattern_file.empty()) ? it->second->pattern_file : m_backup_path + "/" + it->second->pattern_file;
|
||||
plate_data_list[it->first-1]->pattern_bbox_file = (m_load_restore || it->second->pattern_bbox_file.empty()) ? it->second->pattern_bbox_file : m_backup_path + "/" + it->second->pattern_bbox_file;
|
||||
|
@ -2355,6 +2377,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
res = _handle_start_config_plater_instance(attributes, num_attributes);
|
||||
else if (::strcmp(FILAMENT_TAG, name) == 0)
|
||||
res = _handle_start_config_filament(attributes, num_attributes);
|
||||
else if (::strcmp(SLICE_WARNING_TAG, name) == 0)
|
||||
res = _handle_start_config_warning(attributes, num_attributes);
|
||||
else if (::strcmp(ASSEMBLE_TAG, name) == 0)
|
||||
res = _handle_start_assemble(attributes, num_attributes);
|
||||
else if (::strcmp(ASSEMBLE_ITEM_TAG, name) == 0)
|
||||
|
@ -3205,6 +3229,30 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool _BBS_3MF_Importer::_handle_start_config_warning(const char** attributes, unsigned int num_attributes)
|
||||
{
|
||||
if (m_curr_plater) {
|
||||
std::string msg = bbs_get_attribute_value_string(attributes, num_attributes, WARNING_MSG_TAG);
|
||||
std::string lvl_str = bbs_get_attribute_value_string(attributes, num_attributes, "level");
|
||||
GCodeProcessorResult::SliceWarning sw;
|
||||
sw.msg = msg;
|
||||
try {
|
||||
sw.level = atoi(lvl_str.c_str());
|
||||
}
|
||||
catch(...) {
|
||||
};
|
||||
|
||||
m_curr_plater->warnings.push_back(sw);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _BBS_3MF_Importer::_handle_end_config_warning()
|
||||
{
|
||||
// do nothing
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _BBS_3MF_Importer::_handle_start_config_plater(const char** attributes, unsigned int num_attributes)
|
||||
{
|
||||
if (!m_parsing_slice_info) {
|
||||
|
@ -3827,6 +3875,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
bool m_split_model { false }; // save object per file with Production Extention
|
||||
bool m_save_gcode { false }; // whether to save gcode for normal save
|
||||
bool m_skip_model { false }; // skip model when exporting .gcode.3mf
|
||||
bool m_skip_auxiliary { false }; // skip normal axuiliary files
|
||||
|
||||
public:
|
||||
//BBS: add plate data related logic
|
||||
|
@ -3910,6 +3959,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
m_split_model = store_params.strategy & SaveStrategy::SplitModel;
|
||||
m_save_gcode = store_params.strategy & SaveStrategy::WithGcode;
|
||||
m_skip_model = store_params.strategy & SaveStrategy::SkipModel;
|
||||
m_skip_auxiliary = store_params.strategy & SaveStrategy::SkipAuxiliary;
|
||||
|
||||
boost::system::error_code ec;
|
||||
std::string filename = std::string(store_params.path);
|
||||
|
@ -5000,7 +5050,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
// if (volume == nullptr)
|
||||
// continue;
|
||||
|
||||
bool is_left_handed = volume->is_left_handed();
|
||||
//BBS: as we stored matrix seperately, not multiplied into vertex
|
||||
//we don't need to consider this left hand case specially
|
||||
//bool is_left_handed = volume->is_left_handed();
|
||||
bool is_left_handed = false;
|
||||
//VolumeToOffsetsMap::iterator volume_it = volumes_objectID.find(volume);
|
||||
//assert(volume_it != volumes_objectID.end());
|
||||
|
||||
|
@ -5637,6 +5690,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||
<< FILAMENT_USED_M_TAG << "=\"" << it->used_m << "\" "
|
||||
<< FILAMENT_USED_G_TAG << "=\"" << it->used_g << "\" />\n";
|
||||
}
|
||||
|
||||
for (auto it = plate_data->warnings.begin(); it != plate_data->warnings.end(); it++) {
|
||||
stream << " <" << SLICE_WARNING_TAG << " " << "msg=\"" << it->msg << "\" " << "level=\"" << std::to_string(it->level) << "\" />\n";
|
||||
}
|
||||
stream << " </" << PLATE_TAG << ">\n";
|
||||
}
|
||||
}
|
||||
|
@ -5737,7 +5794,7 @@ bool _BBS_3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_ar
|
|||
//BBS
|
||||
std::string gcode = //code.type == CustomGCode::ColorChange ? config->opt_string("color_change_gcode") :
|
||||
code.type == CustomGCode::PausePrint ? config->opt_string("machine_pause_gcode") :
|
||||
//code.type == CustomGCode::Template ? config->opt_string("template_custom_gcode") :
|
||||
code.type == CustomGCode::Template ? config->opt_string("template_custom_gcode") :
|
||||
code.type == CustomGCode::ToolChange ? "tool_change" : code.extra;
|
||||
code_tree.put("<xmlattr>.gcode" , gcode );
|
||||
}
|
||||
|
@ -5826,7 +5883,7 @@ bool _BBS_3MF_Exporter::_add_auxiliary_dir_to_archive(mz_zip_archive &archive, c
|
|||
}
|
||||
continue;
|
||||
}
|
||||
if (boost::filesystem::is_regular_file(dir_entry.path()))
|
||||
if (boost::filesystem::is_regular_file(dir_entry.path()) && !m_skip_auxiliary)
|
||||
{
|
||||
src_file = dir_entry.path().string();
|
||||
dst_in_3mf = dir_entry.path().string();
|
||||
|
|
|
@ -70,6 +70,8 @@ struct PlateData
|
|||
bool is_sliced_valid = false;
|
||||
bool toolpath_outside {false};
|
||||
|
||||
std::vector<GCodeProcessorResult::SliceWarning> warnings;
|
||||
|
||||
std::string get_gcode_prediction_str() {
|
||||
return gcode_prediction;
|
||||
}
|
||||
|
@ -93,6 +95,7 @@ enum class SaveStrategy
|
|||
SkipStatic = 1 << 6,
|
||||
SkipModel = 1 << 7,
|
||||
WithSliceInfo = 1 << 8,
|
||||
SkipAuxiliary = 1 << 9,
|
||||
|
||||
SplitModel = 0x1000 | ProductionExt,
|
||||
Encrypted = SecureContentExt | SplitModel,
|
||||
|
|
|
@ -335,8 +335,8 @@ bool GCode::gcode_label_objects = true;
|
|||
float new_retract_length = full_config.retraction_length.get_at(new_extruder_id);
|
||||
float old_retract_length_toolchange = gcode_writer.extruder() != nullptr ? full_config.retract_length_toolchange.get_at(previous_extruder_id) : 0;
|
||||
float new_retract_length_toolchange = full_config.retract_length_toolchange.get_at(new_extruder_id);
|
||||
int old_filament_temp = gcode_writer.extruder() != nullptr ? full_config.nozzle_temperature.get_at(previous_extruder_id) : 210;
|
||||
int new_filament_temp = full_config.nozzle_temperature.get_at(new_extruder_id);
|
||||
int old_filament_temp = gcode_writer.extruder() != nullptr ? (gcodegen.on_first_layer()? full_config.nozzle_temperature_initial_layer.get_at(previous_extruder_id) : full_config.nozzle_temperature.get_at(previous_extruder_id)) : 210;
|
||||
int new_filament_temp = gcodegen.on_first_layer() ? full_config.nozzle_temperature_initial_layer.get_at(new_extruder_id) : full_config.nozzle_temperature.get_at(new_extruder_id);
|
||||
Vec3d nozzle_pos = gcode_writer.get_position();
|
||||
|
||||
float purge_volume = tcr.purge_volume < EPSILON ? 0 : std::max(tcr.purge_volume, g_min_purge_volume);
|
||||
|
@ -866,7 +866,7 @@ namespace DoExport {
|
|||
//if (ret.size() < MAX_TAGS_COUNT) check(_(L("Printing by object G-code")), config.printing_by_object_gcode.value);
|
||||
//if (ret.size() < MAX_TAGS_COUNT) check(_(L("Color Change G-code")), config.color_change_gcode.value);
|
||||
if (ret.size() < MAX_TAGS_COUNT) check(_(L("Pause G-code")), config.machine_pause_gcode.value);
|
||||
//if (ret.size() < MAX_TAGS_COUNT) check(_(L("Template Custom G-code")), config.template_custom_gcode.value);
|
||||
if (ret.size() < MAX_TAGS_COUNT) check(_(L("Template Custom G-code")), config.template_custom_gcode.value);
|
||||
if (ret.size() < MAX_TAGS_COUNT) {
|
||||
for (const std::string& value : config.filament_start_gcode.values) {
|
||||
check(_(L("Filament start G-code")), value);
|
||||
|
@ -1457,6 +1457,12 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
|||
// Emit machine envelope limits for the Marlin firmware.
|
||||
this->print_machine_envelope(file, print);
|
||||
|
||||
//BBS: emit printing accelerate if has non-zero value
|
||||
if (m_config.default_acceleration.value > 0) {
|
||||
float acceleration = m_config.default_acceleration.value;
|
||||
file.write(m_writer.set_acceleration((unsigned int)floor(acceleration + 0.5)));
|
||||
}
|
||||
|
||||
// Disable fan.
|
||||
if (print.config().close_fan_the_first_x_layers.get_at(initial_extruder_id)) {
|
||||
file.write(m_writer.set_fan(0));
|
||||
|
@ -2224,11 +2230,11 @@ namespace ProcessLayer
|
|||
// Extruder switches are processed by LayerTools, they should be filtered out.
|
||||
assert(custom_gcode->type != CustomGCode::ToolChange);
|
||||
|
||||
CustomGCode::Type gcode_type = custom_gcode->type;
|
||||
CustomGCode::Type gcode_type = custom_gcode->type;
|
||||
bool color_change = gcode_type == CustomGCode::ColorChange;
|
||||
bool tool_change = gcode_type == CustomGCode::ToolChange;
|
||||
bool tool_change = gcode_type == CustomGCode::ToolChange;
|
||||
// Tool Change is applied as Color Change for a single extruder printer only.
|
||||
assert(! tool_change || single_filament_print);
|
||||
assert(!tool_change || single_filament_print);
|
||||
|
||||
std::string pause_print_msg;
|
||||
int m600_extruder_before_layer = -1;
|
||||
|
@ -2236,13 +2242,13 @@ namespace ProcessLayer
|
|||
m600_extruder_before_layer = custom_gcode->extruder - 1;
|
||||
else if (gcode_type == CustomGCode::PausePrint)
|
||||
pause_print_msg = custom_gcode->extra;
|
||||
//BBS: inserting color gcode and template_custom_gcode is removed
|
||||
//BBS: inserting color gcode is removed
|
||||
#if 0
|
||||
// we should add or not colorprint_change in respect to nozzle_diameter count instead of really used extruders count
|
||||
if (color_change || tool_change)
|
||||
{
|
||||
assert(m600_extruder_before_layer >= 0);
|
||||
// Color Change or Tool Change as Color Change.
|
||||
// Color Change or Tool Change as Color Change.
|
||||
// add tag for processor
|
||||
gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Color_Change) + ",T" + std::to_string(m600_extruder_before_layer) + "," + custom_gcode->color + "\n";
|
||||
|
||||
|
@ -2264,20 +2270,19 @@ namespace ProcessLayer
|
|||
// see GH issue #6362
|
||||
gcodegen.writer().unretract();
|
||||
}
|
||||
}
|
||||
else {
|
||||
}
|
||||
else {
|
||||
#endif
|
||||
if (gcode_type == CustomGCode::PausePrint) // Pause print
|
||||
{
|
||||
if (gcode_type == CustomGCode::PausePrint) // Pause print
|
||||
{
|
||||
// add tag for processor
|
||||
gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Pause_Print) + "\n";
|
||||
//! FIXME_in_fw show message during print pause
|
||||
//if (!pause_print_msg.empty())
|
||||
// gcode += "M117 " + pause_print_msg + "\n";
|
||||
//if (!pause_print_msg.empty())
|
||||
// gcode += "M117 " + pause_print_msg + "\n";
|
||||
gcode += gcodegen.placeholder_parser_process("machine_pause_gcode", config.machine_pause_gcode, current_extruder_id) + "\n";
|
||||
}
|
||||
#if 0
|
||||
else {
|
||||
else {
|
||||
// add tag for processor
|
||||
gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Custom_Code) + "\n";
|
||||
if (gcode_type == CustomGCode::Template) // Template Custom Gcode
|
||||
|
@ -2287,9 +2292,9 @@ namespace ProcessLayer
|
|||
|
||||
}
|
||||
gcode += "\n";
|
||||
#if 0
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
return gcode;
|
||||
|
@ -2581,7 +2586,7 @@ GCode::LayerResult GCode::process_layer(
|
|||
for (const LayerToPrint &layer_to_print : layers) {
|
||||
if (layer_to_print.support_layer != nullptr) {
|
||||
const SupportLayer &support_layer = *layer_to_print.support_layer;
|
||||
const PrintObject &object = *support_layer.object();
|
||||
const PrintObject& object = *layer_to_print.original_object;
|
||||
if (! support_layer.support_fills.entities.empty()) {
|
||||
ExtrusionRole role = support_layer.support_fills.role();
|
||||
bool has_support = role == erMixed || role == erSupportMaterial || role == erSupportTransition;
|
||||
|
@ -2666,7 +2671,7 @@ GCode::LayerResult GCode::process_layer(
|
|||
// BBS
|
||||
if (layer_to_print.tree_support_layer != nullptr) {
|
||||
const TreeSupportLayer& tree_support_layer = *layer_to_print.tree_support_layer;
|
||||
const PrintObject& object = *tree_support_layer.object();
|
||||
const PrintObject& object = *layer_to_print.original_object;
|
||||
if (!tree_support_layer.support_fills.entities.empty()) {
|
||||
ExtrusionRole role = tree_support_layer.support_fills.role();
|
||||
bool has_support = role == erMixed || role == erSupportMaterial || role == erSupportTransition;
|
||||
|
@ -2810,7 +2815,7 @@ GCode::LayerResult GCode::process_layer(
|
|||
}
|
||||
printing_extruders.clear();
|
||||
if (is_anything_overridden) {
|
||||
entity_overrides = const_cast<LayerTools&>(layer_tools).wiping_extrusions().get_extruder_overrides(extrusions, correct_extruder_id, layer_to_print.object()->instances().size());
|
||||
entity_overrides = const_cast<LayerTools&>(layer_tools).wiping_extrusions().get_extruder_overrides(extrusions, layer_to_print.original_object, correct_extruder_id, layer_to_print.object()->instances().size());
|
||||
if (entity_overrides == nullptr) {
|
||||
printing_extruders.emplace_back(correct_extruder_id);
|
||||
} else {
|
||||
|
@ -2996,8 +3001,8 @@ GCode::LayerResult GCode::process_layer(
|
|||
|
||||
// BBS
|
||||
WipingExtrusions& wiping_extrusions = const_cast<LayerTools&>(layer_tools).wiping_extrusions();
|
||||
bool support_overridden = wiping_extrusions.is_support_overridden(layer.object());
|
||||
bool support_intf_overridden = wiping_extrusions.is_support_interface_overridden(layer.object());
|
||||
bool support_overridden = wiping_extrusions.is_support_overridden(layer_to_print.original_object);
|
||||
bool support_intf_overridden = wiping_extrusions.is_support_interface_overridden(layer_to_print.original_object);
|
||||
|
||||
ExtrusionRole support_extrusion_role = instance_to_print.object_by_extruder.support_extrusion_role;
|
||||
bool is_overridden = support_extrusion_role == erSupportMaterialInterface ? support_intf_overridden : support_overridden;
|
||||
|
@ -3909,12 +3914,20 @@ bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role)
|
|||
|
||||
if (role == erSupportMaterial || role == erSupportTransition) {
|
||||
const SupportLayer* support_layer = dynamic_cast<const SupportLayer*>(m_layer);
|
||||
|
||||
//FIXME support_layer->support_islands.contains should use some search structure!
|
||||
if (support_layer != NULL && support_layer->support_islands.contains(travel))
|
||||
// skip retraction if this is a travel move inside a support material island
|
||||
//FIXME not retracting over a long path may cause oozing, which in turn may result in missing material
|
||||
// at the end of the extrusion path!
|
||||
return false;
|
||||
|
||||
//reduce the retractions in lightning infills for tree support
|
||||
const TreeSupportLayer* ts_layer = dynamic_cast<const TreeSupportLayer*>(m_layer);
|
||||
if (ts_layer != NULL)
|
||||
for (auto& area : ts_layer->base_areas)
|
||||
if(area.contains(travel))
|
||||
return false;
|
||||
}
|
||||
|
||||
//BBS: need retract when long moving to print perimeter to avoid dropping of material
|
||||
|
@ -4013,7 +4026,7 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
|
|||
// BBS
|
||||
float new_retract_length = m_config.retraction_length.get_at(extruder_id);
|
||||
float new_retract_length_toolchange = m_config.retract_length_toolchange.get_at(extruder_id);
|
||||
int new_filament_temp = m_config.nozzle_temperature.get_at(extruder_id);
|
||||
int new_filament_temp = this->on_first_layer() ? m_config.nozzle_temperature_initial_layer.get_at(extruder_id): m_config.nozzle_temperature.get_at(extruder_id);
|
||||
Vec3d nozzle_pos = m_writer.get_position();
|
||||
float old_retract_length, old_retract_length_toolchange, wipe_volume;
|
||||
int old_filament_temp, old_filament_e_feedrate;
|
||||
|
@ -4029,7 +4042,7 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
|
|||
int previous_extruder_id = m_writer.extruder()->id();
|
||||
old_retract_length = m_config.retraction_length.get_at(previous_extruder_id);
|
||||
old_retract_length_toolchange = m_config.retract_length_toolchange.get_at(previous_extruder_id);
|
||||
old_filament_temp = m_config.nozzle_temperature.get_at(previous_extruder_id);
|
||||
old_filament_temp = this->on_first_layer()? m_config.nozzle_temperature_initial_layer.get_at(previous_extruder_id) : m_config.nozzle_temperature.get_at(previous_extruder_id);
|
||||
wipe_volume = flush_matrix[previous_extruder_id * number_of_extruders + extruder_id];
|
||||
old_filament_e_feedrate = (int)(60.0 * m_config.filament_max_volumetric_speed.get_at(previous_extruder_id) / filament_area);
|
||||
old_filament_e_feedrate = old_filament_e_feedrate == 0 ? 100 : old_filament_e_feedrate;
|
||||
|
|
|
@ -1167,12 +1167,14 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &
|
|||
travel_intersection_count = 0;
|
||||
}
|
||||
|
||||
const ConfigOptionFloat &opt_max_detour = gcodegen.config().max_travel_detour_distance;
|
||||
const ConfigOptionFloatOrPercent &opt_max_detour = gcodegen.config().max_travel_detour_distance;
|
||||
bool max_detour_length_exceeded = false;
|
||||
if (opt_max_detour.value > 0) {
|
||||
double direct_length = travel.length();
|
||||
double detour = result_pl.length() - direct_length;
|
||||
double max_detour_length = scale_(opt_max_detour.value);
|
||||
double max_detour_length = opt_max_detour.percent ?
|
||||
direct_length * 0.01 * opt_max_detour.value :
|
||||
scale_(opt_max_detour.value);
|
||||
if (detour > max_detour_length) {
|
||||
result_pl = {start, end};
|
||||
max_detour_length_exceeded = true;
|
||||
|
|
|
@ -811,6 +811,8 @@ std::string CoolingBuffer::apply_layer_cooldown(
|
|||
//BBS: force to write a fan speed command again
|
||||
if (m_current_fan_speed != -1)
|
||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_current_fan_speed);
|
||||
if (m_additional_fan_speed != -1)
|
||||
new_gcode += GCodeWriter::set_additional_fan(m_additional_fan_speed);
|
||||
}
|
||||
else if (line->type & CoolingLine::TYPE_EXTRUDE_END) {
|
||||
// Just remove this comment.
|
||||
|
|
|
@ -36,6 +36,7 @@ static const float DEFAULT_TRAVEL_ACCELERATION = 1250.0f;
|
|||
static const size_t MIN_EXTRUDERS_COUNT = 5;
|
||||
static const float DEFAULT_FILAMENT_DIAMETER = 1.75f;
|
||||
static const float DEFAULT_FILAMENT_DENSITY = 1.245f;
|
||||
static const int DEFAULT_FILAMENT_VITRIFICATION_TEMPERATURE = 0;
|
||||
static const Slic3r::Vec3f DEFAULT_EXTRUDER_OFFSET = Slic3r::Vec3f::Zero();
|
||||
|
||||
namespace Slic3r {
|
||||
|
@ -776,6 +777,7 @@ void GCodeProcessorResult::reset() {
|
|||
filament_diameters = std::vector<float>(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DIAMETER);
|
||||
filament_densities = std::vector<float>(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DENSITY);
|
||||
custom_gcode_per_print_z = std::vector<CustomGCode::Item>();
|
||||
warnings.clear();
|
||||
|
||||
//BBS: add mutex for protection of gcode result
|
||||
unlock();
|
||||
|
@ -877,6 +879,7 @@ void GCodeProcessor::apply_config(const PrintConfig& config)
|
|||
m_extruder_colors.resize(extruders_count);
|
||||
m_result.filament_diameters.resize(extruders_count);
|
||||
m_result.filament_densities.resize(extruders_count);
|
||||
m_result.filament_vitrification_temperature.resize(extruders_count);
|
||||
m_extruder_temps.resize(extruders_count);
|
||||
|
||||
for (size_t i = 0; i < extruders_count; ++ i) {
|
||||
|
@ -884,6 +887,7 @@ void GCodeProcessor::apply_config(const PrintConfig& config)
|
|||
m_extruder_colors[i] = static_cast<unsigned char>(i);
|
||||
m_result.filament_diameters[i] = static_cast<float>(config.filament_diameter.get_at(i));
|
||||
m_result.filament_densities[i] = static_cast<float>(config.filament_density.get_at(i));
|
||||
m_result.filament_vitrification_temperature[i] = static_cast<float>(config.temperature_vitrification.get_at(i));
|
||||
}
|
||||
|
||||
if (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware) {
|
||||
|
@ -985,6 +989,20 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config)
|
|||
m_result.filament_densities.emplace_back(DEFAULT_FILAMENT_DENSITY);
|
||||
}
|
||||
}
|
||||
//BBS
|
||||
const ConfigOptionInts* filament_vitrification_temperature = config.option<ConfigOptionInts>("temperature_vitrification");
|
||||
if (filament_vitrification_temperature != nullptr) {
|
||||
m_result.filament_vitrification_temperature.clear();
|
||||
m_result.filament_vitrification_temperature.resize(filament_vitrification_temperature->values.size());
|
||||
for (size_t i = 0; i < filament_vitrification_temperature->values.size(); ++i) {
|
||||
m_result.filament_vitrification_temperature[i] = static_cast<int>(filament_vitrification_temperature->values[i]);
|
||||
}
|
||||
}
|
||||
if (m_result.filament_vitrification_temperature.size() < m_result.extruders_count) {
|
||||
for (size_t i = m_result.filament_vitrification_temperature.size(); i < m_result.extruders_count; ++i) {
|
||||
m_result.filament_vitrification_temperature.emplace_back(DEFAULT_FILAMENT_VITRIFICATION_TEMPERATURE);
|
||||
}
|
||||
}
|
||||
|
||||
const ConfigOptionPoints* extruder_offset = config.option<ConfigOptionPoints>("extruder_offset");
|
||||
const ConfigOptionBool* single_extruder_multi_material = config.option<ConfigOptionBool>("single_extruder_multi_material");
|
||||
|
@ -1198,6 +1216,7 @@ void GCodeProcessor::reset()
|
|||
for (size_t i = 0; i < MIN_EXTRUDERS_COUNT; ++i) {
|
||||
m_extruder_temps[i] = 0.0f;
|
||||
}
|
||||
m_highest_bed_temp = 0;
|
||||
|
||||
m_extruded_last_z = 0.0f;
|
||||
m_zero_layer_height = 0.0f;
|
||||
|
@ -1363,6 +1382,8 @@ void GCodeProcessor::finalize(bool post_process)
|
|||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
m_result.time = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - m_start_time).count();
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
//BBS: update slice warning
|
||||
update_slice_warnings();
|
||||
}
|
||||
|
||||
float GCodeProcessor::get_time(PrintEstimatedStatistics::ETimeMode mode) const
|
||||
|
@ -1652,6 +1673,16 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line, bool
|
|||
default: break;
|
||||
}
|
||||
break;
|
||||
case '4':
|
||||
switch (cmd[3]) {
|
||||
case '0': { process_M140(line); break; } // Set bed temperature
|
||||
default: break;
|
||||
}
|
||||
case '9':
|
||||
switch (cmd[3]) {
|
||||
case '0': { process_M190(line); break; } // Wait bed temperature
|
||||
default: break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -3427,6 +3458,21 @@ void GCodeProcessor::process_M135(const GCodeReader::GCodeLine& line)
|
|||
process_T(cmd.substr(pos));
|
||||
}
|
||||
|
||||
void GCodeProcessor::process_M140(const GCodeReader::GCodeLine& line)
|
||||
{
|
||||
float new_temp;
|
||||
if (line.has_value('S', new_temp))
|
||||
m_highest_bed_temp = m_highest_bed_temp < (int)new_temp ? (int)new_temp : m_highest_bed_temp;
|
||||
}
|
||||
|
||||
void GCodeProcessor::process_M190(const GCodeReader::GCodeLine& line)
|
||||
{
|
||||
float new_temp;
|
||||
if (line.has_value('S', new_temp))
|
||||
m_highest_bed_temp = m_highest_bed_temp < (int)new_temp ? (int)new_temp : m_highest_bed_temp;
|
||||
}
|
||||
|
||||
|
||||
void GCodeProcessor::process_M201(const GCodeReader::GCodeLine& line)
|
||||
{
|
||||
// see http://reprap.org/wiki/G-code#M201:_Set_max_printing_acceleration
|
||||
|
@ -3848,6 +3894,15 @@ float GCodeProcessor::get_filament_unload_time(size_t extruder_id)
|
|||
return m_time_processor.extruder_unloaded ? 0.0f : m_time_processor.filament_unload_times;
|
||||
}
|
||||
|
||||
//BBS
|
||||
int GCodeProcessor::get_filament_vitrification_temperature(size_t extrude_id)
|
||||
{
|
||||
if (extrude_id < m_result.filament_vitrification_temperature.size())
|
||||
return m_result.filament_vitrification_temperature[extrude_id];
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
void GCodeProcessor::process_custom_gcode_time(CustomGCode::Type code)
|
||||
{
|
||||
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count); ++i) {
|
||||
|
@ -3909,5 +3964,36 @@ void GCodeProcessor::update_estimated_times_stats()
|
|||
m_result.print_statistics.used_filaments_per_role = m_used_filaments.filaments_per_role;
|
||||
}
|
||||
|
||||
//BBS: ugly code...
|
||||
void GCodeProcessor::update_slice_warnings()
|
||||
{
|
||||
m_result.warnings.clear();
|
||||
|
||||
auto get_used_extruders = [this]() {
|
||||
std::vector<size_t> used_extruders;
|
||||
used_extruders.reserve(m_used_filaments.volumes_per_extruder.size());
|
||||
for (auto item : m_used_filaments.volumes_per_extruder) {
|
||||
used_extruders.push_back(item.first);
|
||||
}
|
||||
return used_extruders;
|
||||
};
|
||||
|
||||
auto used_extruders = get_used_extruders();
|
||||
assert(!used_extruders.empty());
|
||||
if (m_highest_bed_temp != 0) {
|
||||
for (size_t i = 0; i < used_extruders.size(); i++) {
|
||||
int temperature = get_filament_vitrification_temperature(used_extruders[i]);
|
||||
if (temperature != 0 && m_highest_bed_temp > temperature) {
|
||||
GCodeProcessorResult::SliceWarning warning;
|
||||
warning.level = 1;
|
||||
warning.msg = BED_TEMP_TOO_HIGH_THAN_FILAMENT;
|
||||
m_result.warnings.emplace_back(std::move(warning));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_result.warnings.shrink_to_fit();
|
||||
}
|
||||
|
||||
} /* namespace Slic3r */
|
||||
|
||||
|
|
|
@ -16,6 +16,9 @@
|
|||
|
||||
namespace Slic3r {
|
||||
|
||||
// slice warnings enum strings
|
||||
#define BED_TEMP_TOO_HIGH_THAN_FILAMENT "bed_temperature_too_high_than_filament"
|
||||
|
||||
enum class EMoveType : unsigned char
|
||||
{
|
||||
Noop,
|
||||
|
@ -128,6 +131,12 @@ namespace Slic3r {
|
|||
}
|
||||
};
|
||||
|
||||
struct SliceWarning {
|
||||
int level; // 0: normal tips, 1: warning; 2: error
|
||||
std::string msg; // enum string
|
||||
std::vector<std::string> params; // extra msg info
|
||||
};
|
||||
|
||||
std::string filename;
|
||||
unsigned int id;
|
||||
std::vector<MoveVertex> moves;
|
||||
|
@ -144,8 +153,11 @@ namespace Slic3r {
|
|||
std::vector<std::string> extruder_colors;
|
||||
std::vector<float> filament_diameters;
|
||||
std::vector<float> filament_densities;
|
||||
std::vector<int> filament_vitrification_temperature;
|
||||
PrintEstimatedStatistics print_statistics;
|
||||
std::vector<CustomGCode::Item> custom_gcode_per_print_z;
|
||||
//BBS
|
||||
std::vector<SliceWarning> warnings;
|
||||
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
int64_t time{ 0 };
|
||||
|
@ -171,6 +183,7 @@ namespace Slic3r {
|
|||
filament_densities = other.filament_densities;
|
||||
print_statistics = other.print_statistics;
|
||||
custom_gcode_per_print_z = other.custom_gcode_per_print_z;
|
||||
warnings = other.warnings;
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
time = other.time;
|
||||
#endif
|
||||
|
@ -593,6 +606,7 @@ namespace Slic3r {
|
|||
unsigned char m_last_extruder_id;
|
||||
ExtruderColors m_extruder_colors;
|
||||
ExtruderTemps m_extruder_temps;
|
||||
int m_highest_bed_temp;
|
||||
float m_extruded_last_z;
|
||||
float m_first_layer_height; // mm
|
||||
float m_zero_layer_height; // mm
|
||||
|
@ -759,6 +773,12 @@ namespace Slic3r {
|
|||
// Set tool (MakerWare)
|
||||
void process_M135(const GCodeReader::GCodeLine& line);
|
||||
|
||||
//BBS: Set bed temperature
|
||||
void process_M140(const GCodeReader::GCodeLine& line);
|
||||
|
||||
//BBS: wait bed temperature
|
||||
void process_M190(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set max printing acceleration
|
||||
void process_M201(const GCodeReader::GCodeLine& line);
|
||||
|
||||
|
@ -811,6 +831,7 @@ namespace Slic3r {
|
|||
void set_travel_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value);
|
||||
float get_filament_load_time(size_t extruder_id);
|
||||
float get_filament_unload_time(size_t extruder_id);
|
||||
int get_filament_vitrification_temperature(size_t extrude_id);
|
||||
|
||||
void process_custom_gcode_time(CustomGCode::Type code);
|
||||
void process_filaments(CustomGCode::Type code);
|
||||
|
@ -819,6 +840,8 @@ namespace Slic3r {
|
|||
void simulate_st_synchronize(float additional_time = 0.0f);
|
||||
|
||||
void update_estimated_times_stats();
|
||||
//BBS:
|
||||
void update_slice_warnings();
|
||||
};
|
||||
|
||||
} /* namespace Slic3r */
|
||||
|
|
|
@ -864,11 +864,11 @@ const LayerTools& ToolOrdering::tools_for_layer(coordf_t print_z) const
|
|||
}
|
||||
|
||||
// This function is called from Print::mark_wiping_extrusions and sets extruder this entity should be printed with (-1 .. as usual)
|
||||
void WipingExtrusions::set_extruder_override(const ExtrusionEntity* entity, size_t copy_id, int extruder, size_t num_of_copies)
|
||||
void WipingExtrusions::set_extruder_override(const ExtrusionEntity* entity, const PrintObject* object, size_t copy_id, int extruder, size_t num_of_copies)
|
||||
{
|
||||
something_overridden = true;
|
||||
|
||||
auto entity_map_it = (entity_map.emplace(entity, ExtruderPerCopy())).first; // (add and) return iterator
|
||||
auto entity_map_it = (entity_map.emplace(std::make_tuple(entity, object), ExtruderPerCopy())).first; // (add and) return iterator
|
||||
ExtruderPerCopy& copies_vector = entity_map_it->second;
|
||||
copies_vector.resize(num_of_copies, -1);
|
||||
|
||||
|
@ -958,12 +958,20 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int
|
|||
return std::max(0.f, volume_to_wipe); // Soluble filament cannot be wiped in a random infill, neither the filament after it
|
||||
|
||||
// BBS
|
||||
if (print.config().filament_is_support.get_at(old_extruder))
|
||||
if (print.config().filament_is_support.get_at(old_extruder) || print.config().filament_is_support.get_at(new_extruder))
|
||||
return std::max(0.f, volume_to_wipe); // Support filament cannot be used to print support, infill, wipe_tower, etc.
|
||||
|
||||
// we will sort objects so that dedicated for wiping are at the beginning:
|
||||
ConstPrintObjectPtrs object_list = print.objects().vector();
|
||||
std::sort(object_list.begin(), object_list.end(), [](const PrintObject* a, const PrintObject* b) { return a->config().flush_into_objects; });
|
||||
// BBS: fix the exception caused by not fixed order between different objects
|
||||
std::sort(object_list.begin(), object_list.end(), [object_list](const PrintObject* a, const PrintObject* b) {
|
||||
if (a->config().flush_into_objects != b->config().flush_into_objects) {
|
||||
return a->config().flush_into_objects.getBool();
|
||||
}
|
||||
else {
|
||||
return a->id() < b->id();
|
||||
}
|
||||
});
|
||||
|
||||
// We will now iterate through
|
||||
// - first the dedicated objects to mark perimeters or infills (depending on infill_first)
|
||||
|
@ -1011,9 +1019,9 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int
|
|||
if (!lt.is_extruder_order(lt.wall_filament(region), new_extruder))
|
||||
continue;
|
||||
|
||||
if ((!is_entity_overridden(fill, copy) && fill->total_volume() > min_infill_volume))
|
||||
if ((!is_entity_overridden(fill, object, copy) && fill->total_volume() > min_infill_volume))
|
||||
{ // this infill will be used to wipe this extruder
|
||||
set_extruder_override(fill, copy, new_extruder, num_of_copies);
|
||||
set_extruder_override(fill, object, copy, new_extruder, num_of_copies);
|
||||
if ((volume_to_wipe -= float(fill->total_volume())) <= 0.f)
|
||||
// More material was purged already than asked for.
|
||||
return 0.f;
|
||||
|
@ -1026,8 +1034,8 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int
|
|||
{
|
||||
for (const ExtrusionEntity* ee : layerm->perimeters.entities) {
|
||||
auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
|
||||
if (is_overriddable(*fill, print.config(), *object, region) && !is_entity_overridden(fill, copy) && fill->total_volume() > min_infill_volume) {
|
||||
set_extruder_override(fill, copy, new_extruder, num_of_copies);
|
||||
if (is_overriddable(*fill, print.config(), *object, region) && !is_entity_overridden(fill, object, copy) && fill->total_volume() > min_infill_volume) {
|
||||
set_extruder_override(fill, object, copy, new_extruder, num_of_copies);
|
||||
if ((volume_to_wipe -= float(fill->total_volume())) <= 0.f)
|
||||
// More material was purged already than asked for.
|
||||
return 0.f;
|
||||
|
@ -1117,7 +1125,7 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print)
|
|||
auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
|
||||
|
||||
if (!is_overriddable(*fill, print.config(), *object, region)
|
||||
|| is_entity_overridden(fill, copy) )
|
||||
|| is_entity_overridden(fill, object, copy) )
|
||||
continue;
|
||||
|
||||
// This infill could have been overridden but was not - unless we do something, it could be
|
||||
|
@ -1130,7 +1138,7 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print)
|
|||
//|| object->config().flush_into_objects // in this case the perimeter is overridden, so we can override by the last one safely
|
||||
|| lt.is_extruder_order(lt.wall_filament(region), last_nonsoluble_extruder // !infill_first, but perimeter is already printed when last extruder prints
|
||||
|| ! lt.has_extruder(lt.sparse_infill_filament(region)))) // we have to force override - this could violate infill_first (FIXME)
|
||||
set_extruder_override(fill, copy, (is_infill_first ? first_nonsoluble_extruder : last_nonsoluble_extruder), num_of_copies);
|
||||
set_extruder_override(fill, object, copy, (is_infill_first ? first_nonsoluble_extruder : last_nonsoluble_extruder), num_of_copies);
|
||||
else {
|
||||
// In this case we can (and should) leave it to be printed normally.
|
||||
// Force overriding would mean it gets printed before its perimeter.
|
||||
|
@ -1140,8 +1148,8 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print)
|
|||
// Now the same for perimeters - see comments above for explanation:
|
||||
for (const ExtrusionEntity* ee : layerm->perimeters.entities) { // iterate through all perimeter Collections
|
||||
auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
|
||||
if (is_overriddable(*fill, print.config(), *object, region) && ! is_entity_overridden(fill, copy))
|
||||
set_extruder_override(fill, copy, (is_infill_first ? last_nonsoluble_extruder : first_nonsoluble_extruder), num_of_copies);
|
||||
if (is_overriddable(*fill, print.config(), *object, region) && ! is_entity_overridden(fill, object, copy))
|
||||
set_extruder_override(fill, object, copy, (is_infill_first ? last_nonsoluble_extruder : first_nonsoluble_extruder), num_of_copies);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1154,10 +1162,10 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print)
|
|||
// so -1 was used as "print as usual").
|
||||
// The resulting vector therefore keeps track of which extrusions are the ones that were overridden and which were not. If the extruder used is overridden,
|
||||
// its number is saved as is (zero-based index). Regular extrusions are saved as -number-1 (unfortunately there is no negative zero).
|
||||
const WipingExtrusions::ExtruderPerCopy* WipingExtrusions::get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, size_t num_of_copies)
|
||||
const WipingExtrusions::ExtruderPerCopy* WipingExtrusions::get_extruder_overrides(const ExtrusionEntity* entity, const PrintObject* object, int correct_extruder_id, size_t num_of_copies)
|
||||
{
|
||||
ExtruderPerCopy *overrides = nullptr;
|
||||
auto entity_map_it = entity_map.find(entity);
|
||||
auto entity_map_it = entity_map.find(std::make_tuple(entity, object));
|
||||
if (entity_map_it != entity_map.end()) {
|
||||
overrides = &entity_map_it->second;
|
||||
overrides->resize(num_of_copies, -1);
|
||||
|
|
|
@ -31,7 +31,7 @@ public:
|
|||
typedef boost::container::small_vector<int32_t, 3> ExtruderPerCopy;
|
||||
|
||||
// This is called from GCode::process_layer - see implementation for further comments:
|
||||
const ExtruderPerCopy* get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, size_t num_of_copies);
|
||||
const ExtruderPerCopy* get_extruder_overrides(const ExtrusionEntity* entity, const PrintObject* object, int correct_extruder_id, size_t num_of_copies);
|
||||
int get_support_extruder_overrides(const PrintObject* object);
|
||||
int get_support_interface_extruder_overrides(const PrintObject* object);
|
||||
|
||||
|
@ -71,18 +71,18 @@ private:
|
|||
int last_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const;
|
||||
|
||||
// This function is called from mark_wiping_extrusions and sets extruder that it should be printed with (-1 .. as usual)
|
||||
void set_extruder_override(const ExtrusionEntity* entity, size_t copy_id, int extruder, size_t num_of_copies);
|
||||
void set_extruder_override(const ExtrusionEntity* entity, const PrintObject* object, size_t copy_id, int extruder, size_t num_of_copies);
|
||||
// BBS
|
||||
void set_support_extruder_override(const PrintObject* object, size_t copy_id, int extruder, size_t num_of_copies);
|
||||
void set_support_interface_extruder_override(const PrintObject* object, size_t copy_id, int extruder, size_t num_of_copies);
|
||||
|
||||
// Returns true in case that entity is not printed with its usual extruder for a given copy:
|
||||
bool is_entity_overridden(const ExtrusionEntity* entity, size_t copy_id) const {
|
||||
auto it = entity_map.find(entity);
|
||||
bool is_entity_overridden(const ExtrusionEntity* entity, const PrintObject *object, size_t copy_id) const {
|
||||
auto it = entity_map.find(std::make_tuple(entity, object));
|
||||
return it == entity_map.end() ? false : it->second[copy_id] != -1;
|
||||
}
|
||||
|
||||
std::map<const ExtrusionEntity*, ExtruderPerCopy> entity_map; // to keep track of who prints what
|
||||
std::map<std::tuple<const ExtrusionEntity*, const PrintObject *>, ExtruderPerCopy> entity_map; // to keep track of who prints what
|
||||
// BBS
|
||||
std::map<const PrintObject*, int> support_map;
|
||||
std::map<const PrintObject*, int> support_intf_map;
|
||||
|
|
103
src/libslic3r/Geometry/VoronoiUtilsCgal.cpp
Normal file
103
src/libslic3r/Geometry/VoronoiUtilsCgal.cpp
Normal file
|
@ -0,0 +1,103 @@
|
|||
#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
|
||||
#include <CGAL/Arr_segment_traits_2.h>
|
||||
#include <CGAL/Surface_sweep_2_algorithms.h>
|
||||
|
||||
#include "libslic3r/Geometry/Voronoi.hpp"
|
||||
#include "libslic3r/Arachne/utils/VoronoiUtils.hpp"
|
||||
|
||||
#include "VoronoiUtilsCgal.hpp"
|
||||
|
||||
using VD = Slic3r::Geometry::VoronoiDiagram;
|
||||
|
||||
namespace Slic3r::Geometry {
|
||||
|
||||
using CGAL_Point = CGAL::Exact_predicates_exact_constructions_kernel::Point_2;
|
||||
using CGAL_Segment = CGAL::Arr_segment_traits_2<CGAL::Exact_predicates_exact_constructions_kernel>::Curve_2;
|
||||
|
||||
inline static CGAL_Point to_cgal_point(const VD::vertex_type &pt) { return {pt.x(), pt.y()}; }
|
||||
|
||||
// FIXME Lukas H.: Also includes parabolic segments.
|
||||
bool VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(const VD &voronoi_diagram)
|
||||
{
|
||||
assert(std::all_of(voronoi_diagram.edges().cbegin(), voronoi_diagram.edges().cend(),
|
||||
[](const VD::edge_type &edge) { return edge.color() == 0; }));
|
||||
|
||||
std::vector<CGAL_Segment> segments;
|
||||
segments.reserve(voronoi_diagram.num_edges());
|
||||
|
||||
for (const VD::edge_type &edge : voronoi_diagram.edges()) {
|
||||
if (edge.color() != 0)
|
||||
continue;
|
||||
|
||||
if (edge.is_finite() && edge.is_linear() && edge.vertex0() != nullptr && edge.vertex1() != nullptr &&
|
||||
Arachne::VoronoiUtils::is_finite(*edge.vertex0()) && Arachne::VoronoiUtils::is_finite(*edge.vertex1())) {
|
||||
segments.emplace_back(to_cgal_point(*edge.vertex0()), to_cgal_point(*edge.vertex1()));
|
||||
edge.color(1);
|
||||
assert(edge.twin() != nullptr);
|
||||
edge.twin()->color(1);
|
||||
}
|
||||
}
|
||||
|
||||
for (const VD::edge_type &edge : voronoi_diagram.edges())
|
||||
edge.color(0);
|
||||
|
||||
std::vector<CGAL_Point> intersections_pt;
|
||||
CGAL::compute_intersection_points(segments.begin(), segments.end(), std::back_inserter(intersections_pt));
|
||||
return intersections_pt.empty();
|
||||
}
|
||||
|
||||
static bool check_if_three_vectors_are_ccw(const CGAL_Point &common_pt, const CGAL_Point &pt_1, const CGAL_Point &pt_2, const CGAL_Point &test_pt) {
|
||||
CGAL::Orientation orientation = CGAL::orientation(common_pt, pt_1, pt_2);
|
||||
if (orientation == CGAL::Orientation::COLLINEAR) {
|
||||
// The first two edges are collinear, so the third edge must be on the right side on the first of them.
|
||||
return CGAL::orientation(common_pt, pt_1, test_pt) == CGAL::Orientation::RIGHT_TURN;
|
||||
} else if (orientation == CGAL::Orientation::LEFT_TURN) {
|
||||
// CCW oriented angle between vectors (common_pt, pt1) and (common_pt, pt2) is bellow PI.
|
||||
// So we need to check if test_pt isn't between them.
|
||||
CGAL::Orientation orientation1 = CGAL::orientation(common_pt, pt_1, test_pt);
|
||||
CGAL::Orientation orientation2 = CGAL::orientation(common_pt, pt_2, test_pt);
|
||||
return (orientation1 != CGAL::Orientation::LEFT_TURN || orientation2 != CGAL::Orientation::RIGHT_TURN);
|
||||
} else {
|
||||
assert(orientation == CGAL::Orientation::RIGHT_TURN);
|
||||
// CCW oriented angle between vectors (common_pt, pt1) and (common_pt, pt2) is upper PI.
|
||||
// So we need to check if test_pt is between them.
|
||||
CGAL::Orientation orientation1 = CGAL::orientation(common_pt, pt_1, test_pt);
|
||||
CGAL::Orientation orientation2 = CGAL::orientation(common_pt, pt_2, test_pt);
|
||||
return (orientation1 == CGAL::Orientation::RIGHT_TURN || orientation2 == CGAL::Orientation::LEFT_TURN);
|
||||
}
|
||||
}
|
||||
|
||||
bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram)
|
||||
{
|
||||
for (const VD::vertex_type &vertex : voronoi_diagram.vertices()) {
|
||||
std::vector<const VD::edge_type *> edges;
|
||||
const VD::edge_type *edge = vertex.incident_edge();
|
||||
|
||||
do {
|
||||
// FIXME Lukas H.: Also process parabolic segments.
|
||||
if (edge->is_finite() && edge->is_linear() && edge->vertex0() != nullptr && edge->vertex1() != nullptr &&
|
||||
Arachne::VoronoiUtils::is_finite(*edge->vertex0()) && Arachne::VoronoiUtils::is_finite(*edge->vertex1()))
|
||||
edges.emplace_back(edge);
|
||||
|
||||
edge = edge->rot_next();
|
||||
} while (edge != vertex.incident_edge());
|
||||
|
||||
// Checking for CCW make sense for three and more edges.
|
||||
if (edges.size() > 2) {
|
||||
for (auto edge_it = edges.begin() ; edge_it != edges.end(); ++edge_it) {
|
||||
const Geometry::VoronoiDiagram::edge_type *prev_edge = edge_it == edges.begin() ? edges.back() : *std::prev(edge_it);
|
||||
const Geometry::VoronoiDiagram::edge_type *curr_edge = *edge_it;
|
||||
const Geometry::VoronoiDiagram::edge_type *next_edge = std::next(edge_it) == edges.end() ? edges.front() : *std::next(edge_it);
|
||||
|
||||
if (!check_if_three_vectors_are_ccw(to_cgal_point(*prev_edge->vertex0()), to_cgal_point(*prev_edge->vertex1()),
|
||||
to_cgal_point(*curr_edge->vertex1()), to_cgal_point(*next_edge->vertex1())))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
} // namespace Slic3r::Geometry
|
21
src/libslic3r/Geometry/VoronoiUtilsCgal.hpp
Normal file
21
src/libslic3r/Geometry/VoronoiUtilsCgal.hpp
Normal file
|
@ -0,0 +1,21 @@
|
|||
#ifndef slic3r_VoronoiUtilsCgal_hpp_
|
||||
#define slic3r_VoronoiUtilsCgal_hpp_
|
||||
|
||||
#include "Voronoi.hpp"
|
||||
|
||||
namespace Slic3r::Geometry {
|
||||
class VoronoiDiagram;
|
||||
|
||||
class VoronoiUtilsCgal
|
||||
{
|
||||
public:
|
||||
// Check if the Voronoi diagram is planar using CGAL sweeping edge algorithm for enumerating all intersections between lines.
|
||||
static bool is_voronoi_diagram_planar_intersection(const VoronoiDiagram &voronoi_diagram);
|
||||
|
||||
// Check if the Voronoi diagram is planar using verification that all neighboring edges are ordered CCW for each vertex.
|
||||
static bool is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram);
|
||||
|
||||
};
|
||||
} // namespace Slic3r::Geometry
|
||||
|
||||
#endif // slic3r_VoronoiUtilsCgal_hpp_
|
|
@ -19,6 +19,10 @@ namespace FillAdaptive {
|
|||
struct Octree;
|
||||
};
|
||||
|
||||
namespace FillLightning {
|
||||
class Generator;
|
||||
};
|
||||
|
||||
class LayerRegion
|
||||
{
|
||||
public:
|
||||
|
@ -166,7 +170,7 @@ public:
|
|||
void make_perimeters();
|
||||
// Phony version of make_fills() without parameters for Perl integration only.
|
||||
void make_fills() { this->make_fills(nullptr, nullptr); }
|
||||
void make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree);
|
||||
void make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator = nullptr);
|
||||
void make_ironing();
|
||||
|
||||
void export_region_slices_to_svg(const char *path) const;
|
||||
|
@ -245,7 +249,7 @@ public:
|
|||
ExPolygons floor_areas;
|
||||
ExPolygons base_areas;
|
||||
|
||||
enum AreaType {
|
||||
enum AreaType {
|
||||
BaseType=0,
|
||||
RoofType=1,
|
||||
FloorType=2,
|
||||
|
|
|
@ -118,7 +118,8 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
|
|||
const bool has_infill = this->region().config().sparse_infill_density.value > 0.;
|
||||
//BBS
|
||||
auto nozzle_diameter = this->region().nozzle_dmr_avg(this->layer()->object()->print()->config());
|
||||
const float margin = std::min(float(scale_(EXTERNAL_INFILL_MARGIN)), float(scale_(nozzle_diameter * EXTERNAL_INFILL_MARGIN / 0.4)));
|
||||
const float margin = float(scale_(EXTERNAL_INFILL_MARGIN));
|
||||
const float bridge_margin = std::min(float(scale_(BRIDGE_INFILL_MARGIN)), float(scale_(nozzle_diameter * BRIDGE_INFILL_MARGIN / 0.4)));
|
||||
|
||||
// BBS
|
||||
const PrintObjectConfig& object_config = this->layer()->object()->config();
|
||||
|
@ -224,7 +225,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
|
|||
break;
|
||||
}
|
||||
// Grown by 3mm.
|
||||
Polygons polys = offset(bridges[i].expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS);
|
||||
Polygons polys = offset(bridges[i].expolygon, bridge_margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS);
|
||||
if (idx_island == -1) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "Bridge did not fall into the source region!";
|
||||
} else {
|
||||
|
|
|
@ -755,12 +755,14 @@ std::string Model::get_backup_path()
|
|||
{
|
||||
if (backup_path.empty())
|
||||
{
|
||||
auto pid = get_current_pid();
|
||||
boost::filesystem::path parent_path(temporary_dir());
|
||||
std::time_t t = std::time(0);
|
||||
std::tm* now_time = std::localtime(&t);
|
||||
std::stringstream buf;
|
||||
buf << "/bamboo_model/";
|
||||
buf << std::put_time(now_time, "%a_%b_%d/%H_%M_%S#");
|
||||
buf << pid << "#";
|
||||
buf << this->id().id;
|
||||
|
||||
backup_path = parent_path.string() + buf.str();
|
||||
|
|
|
@ -222,29 +222,47 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
|
|||
if (perimeter_generator.config->detect_overhang_wall && perimeter_generator.layer_id > perimeter_generator.object_config->raft_layers) {
|
||||
// get non 100% overhang paths by intersecting this loop with the grown lower slices
|
||||
Polylines remain_polines;
|
||||
for (auto it = lower_polygons_series->begin();
|
||||
it != lower_polygons_series->end(); it++)
|
||||
{
|
||||
|
||||
Polylines inside_polines = (it == lower_polygons_series->begin()) ?
|
||||
intersection_pl({ polygon }, it->second) :
|
||||
intersection_pl(remain_polines, it->second);
|
||||
if (perimeter_generator.config->enable_overhang_speed) {
|
||||
for (auto it = lower_polygons_series->begin();
|
||||
it != lower_polygons_series->end(); it++)
|
||||
{
|
||||
|
||||
Polylines inside_polines = (it == lower_polygons_series->begin()) ?
|
||||
intersection_pl({ polygon }, it->second) :
|
||||
intersection_pl(remain_polines, it->second);
|
||||
extrusion_paths_append(
|
||||
paths,
|
||||
std::move(inside_polines),
|
||||
it->first,
|
||||
int(0),
|
||||
role,
|
||||
extrusion_mm3_per_mm,
|
||||
extrusion_width,
|
||||
(float)perimeter_generator.layer_height);
|
||||
|
||||
remain_polines = (it == lower_polygons_series->begin()) ?
|
||||
diff_pl({ polygon }, it->second) :
|
||||
diff_pl(remain_polines, it->second);
|
||||
|
||||
if (remain_polines.size() == 0)
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
auto it = lower_polygons_series->end();
|
||||
it--;
|
||||
Polylines inside_polines = intersection_pl({ polygon }, it->second);
|
||||
extrusion_paths_append(
|
||||
paths,
|
||||
std::move(inside_polines),
|
||||
it->first,
|
||||
int(0),
|
||||
int(0),
|
||||
role,
|
||||
extrusion_mm3_per_mm,
|
||||
extrusion_width,
|
||||
(float)perimeter_generator.layer_height);
|
||||
|
||||
remain_polines = (it == lower_polygons_series->begin())?
|
||||
diff_pl({ polygon }, it->second) :
|
||||
diff_pl(remain_polines, it->second);
|
||||
|
||||
if (remain_polines.size() == 0)
|
||||
break;
|
||||
remain_polines = diff_pl({ polygon }, it->second);
|
||||
}
|
||||
|
||||
// get 100% overhang paths by checking what parts of this loop fall
|
||||
|
|
|
@ -940,31 +940,31 @@ namespace client
|
|||
auto error_line = std::string(first, first_pos) + std::string(last, 0, last_pos);
|
||||
// Position of the it_error from the start of its line.
|
||||
auto error_pos = (it_error - it_begin) - first_pos;
|
||||
msg += Slic3r::format(_(L("Error at line %1%:\n")), std::to_string(line_nr));
|
||||
//if (! info.tag.empty() && info.tag.front() == '*') {
|
||||
// // The gat contains an explanatory string.
|
||||
// msg += ": ";
|
||||
// msg += info.tag.substr(1);
|
||||
//} else {
|
||||
// auto it = tag_to_error_message.find(info.tag);
|
||||
// if (it == tag_to_error_message.end()) {
|
||||
// // A generic error report based on the nonterminal or terminal symbol name.
|
||||
// msg += ". Expecting tag ";
|
||||
// msg += info.tag;
|
||||
// } else {
|
||||
// // Use the human readable error message.
|
||||
// msg += ". ";
|
||||
// msg += it->second;
|
||||
// }
|
||||
//}
|
||||
//msg += '\n';
|
||||
msg += "Parsing error at line " + std::to_string(line_nr);
|
||||
if (! info.tag.empty() && info.tag.front() == '*') {
|
||||
// The gat contains an explanatory string.
|
||||
msg += ": ";
|
||||
msg += info.tag.substr(1);
|
||||
} else {
|
||||
auto it = tag_to_error_message.find(info.tag);
|
||||
if (it == tag_to_error_message.end()) {
|
||||
// A generic error report based on the nonterminal or terminal symbol name.
|
||||
msg += ". Expecting tag ";
|
||||
msg += info.tag;
|
||||
} else {
|
||||
// Use the human readable error message.
|
||||
msg += ". ";
|
||||
msg += it->second;
|
||||
}
|
||||
}
|
||||
msg += '\n';
|
||||
// This hack removes all non-UTF8 characters from the source line, so that the upstream wxWidgets conversions
|
||||
// from UTF8 to UTF16 don't bail out.
|
||||
msg += boost::nowide::narrow(boost::nowide::widen(error_line));
|
||||
msg += '\n';
|
||||
//for (size_t i = 0; i < error_pos; ++ i)
|
||||
// msg += ' ';
|
||||
//msg += "^\n";
|
||||
for (size_t i = 0; i < error_pos; ++ i)
|
||||
msg += ' ';
|
||||
msg += "^\n";
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -180,6 +180,11 @@ inline bool operator<(const Point &l, const Point &r)
|
|||
return l.x() < r.x() || (l.x() == r.x() && l.y() < r.y());
|
||||
}
|
||||
|
||||
inline Point operator* (const Point& l, const double& r)
|
||||
{
|
||||
return { coord_t(l.x() * r), coord_t(l.y() * r) };
|
||||
}
|
||||
|
||||
inline bool is_approx(const Point &p1, const Point &p2, coord_t epsilon = coord_t(SCALED_EPSILON))
|
||||
{
|
||||
Point d = (p2 - p1).cwiseAbs();
|
||||
|
|
|
@ -422,7 +422,7 @@ bool Polyline::split_fitting_result_before_index(const size_t index, Point& new_
|
|||
if (data.back().is_arc_move() && data.back().end_point_index > index) {
|
||||
if (!data.back().arc_data.clip_end(this->points[index]))
|
||||
//BBS: failed to clip arc, then return to be linear move
|
||||
data.back().path_type == EMovePathType::Linear_move;
|
||||
data.back().path_type = EMovePathType::Linear_move;
|
||||
else
|
||||
//BBS: succeed to clip arc, then update and return the new end point
|
||||
new_endpoint = data.back().arc_data.end_point;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "Exception.hpp"
|
||||
#include "Preset.hpp"
|
||||
#include "PresetBundle.hpp"
|
||||
#include "AppConfig.hpp"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
@ -643,6 +644,44 @@ std::string Preset::get_filament_type(std::string &display_filament_type)
|
|||
return config.get_filament_type(display_filament_type);
|
||||
}
|
||||
|
||||
std::string Preset::get_printer_type(PresetBundle *preset_bundle)
|
||||
{
|
||||
if (preset_bundle) {
|
||||
auto config = &preset_bundle->printers.get_edited_preset().config;
|
||||
std::string vendor_name;
|
||||
for (auto vendor_profile : preset_bundle->vendors) {
|
||||
for (auto vendor_model : vendor_profile.second.models)
|
||||
if (vendor_model.name == config->opt_string("printer_model"))
|
||||
{
|
||||
vendor_name = vendor_profile.first;
|
||||
return vendor_model.model_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
bool Preset::is_bbl_vendor_preset(PresetBundle *preset_bundle)
|
||||
{
|
||||
bool is_bbl_vendor_preset = true;
|
||||
if (preset_bundle) {
|
||||
auto config = &preset_bundle->printers.get_edited_preset().config;
|
||||
std::string vendor_name;
|
||||
for (auto vendor_profile : preset_bundle->vendors) {
|
||||
for (auto vendor_model : vendor_profile.second.models)
|
||||
if (vendor_model.name == config->opt_string("printer_model"))
|
||||
{
|
||||
vendor_name = vendor_profile.first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!vendor_name.empty())
|
||||
is_bbl_vendor_preset = vendor_name.compare("BBL") == 0 ? true : false;
|
||||
}
|
||||
return is_bbl_vendor_preset;
|
||||
}
|
||||
|
||||
static std::vector<std::string> s_Preset_print_options {
|
||||
"layer_height", "initial_layer_print_height", "wall_loops", "slice_closing_radius", "spiral_mode",
|
||||
"top_shell_layers", "top_shell_thickness", "bottom_shell_layers", "bottom_shell_thickness",
|
||||
|
@ -728,7 +767,7 @@ static std::vector<std::string> s_Preset_printer_options {
|
|||
"default_print_profile", "inherits",
|
||||
"silent_mode",
|
||||
// BBS
|
||||
"scan_first_layer", "machine_load_filament_time", "machine_unload_filament_time", "machine_pause_gcode",
|
||||
"scan_first_layer", "machine_load_filament_time", "machine_unload_filament_time", "machine_pause_gcode", "template_custom_gcode",
|
||||
"nozzle_type", "nozzle_diameter", "auxiliary_fan", "nozzle_volume",
|
||||
//SoftFever
|
||||
"host_type", "print_host", "printhost_apikey",
|
||||
|
|
|
@ -296,6 +296,9 @@ public:
|
|||
|
||||
// special for upport G and Support W
|
||||
std::string get_filament_type(std::string &display_filament_type);
|
||||
std::string get_printer_type(PresetBundle *preset_bundle);
|
||||
|
||||
bool is_bbl_vendor_preset(PresetBundle *preset_bundle);
|
||||
|
||||
static const std::vector<std::string>& print_options();
|
||||
static const std::vector<std::string>& filament_options();
|
||||
|
|
|
@ -179,8 +179,13 @@ void PresetBundle::setup_directories()
|
|||
boost::filesystem::path subdir = path;
|
||||
subdir.make_preferred();
|
||||
if (! boost::filesystem::is_directory(subdir) &&
|
||||
! boost::filesystem::create_directory(subdir))
|
||||
throw Slic3r::RuntimeError(std::string("Unable to create directory ") + subdir.string());
|
||||
! boost::filesystem::create_directory(subdir)) {
|
||||
if (boost::filesystem::is_directory(subdir)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << boost::format("creating directory %1% failed, maybe created by other instance, go on!")%subdir.string();
|
||||
}
|
||||
else
|
||||
throw Slic3r::RuntimeError(std::string("Unable to create directory ") + subdir.string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -247,6 +252,10 @@ PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, Forward
|
|||
else {
|
||||
dir_user_presets = data_dir() + "/" + PRESET_USER_DIR + "/default";
|
||||
}
|
||||
fs::path user_folder(data_dir() + "/" + PRESET_USER_DIR);
|
||||
if (!fs::exists(user_folder))
|
||||
fs::create_directory(user_folder);
|
||||
|
||||
fs::path folder(dir_user_presets);
|
||||
if (!fs::exists(folder))
|
||||
fs::create_directory(folder);
|
||||
|
@ -659,7 +668,7 @@ PresetsConfigSubstitutions PresetBundle::import_presets(std::vector<std::string>
|
|||
continue;
|
||||
}
|
||||
new_config.apply(std::move(config));
|
||||
|
||||
|
||||
Preset &preset = collection->load_preset(collection->path_from_name(name), name, std::move(new_config), false);
|
||||
preset.is_external = true;
|
||||
preset.version = *version;
|
||||
|
@ -695,6 +704,11 @@ void PresetBundle::save_user_presets(AppConfig& config, std::vector<std::string>
|
|||
const std::string dir_user_presets = data_dir() + "/" + PRESET_USER_DIR + "/"+ config.get("preset_folder");
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" enter, save to %1%")%dir_user_presets;
|
||||
|
||||
fs::path user_folder(data_dir() + "/" + PRESET_USER_DIR);
|
||||
if (!fs::exists(user_folder))
|
||||
fs::create_directory(user_folder);
|
||||
|
||||
fs::path folder(dir_user_presets);
|
||||
if (!fs::exists(folder))
|
||||
fs::create_directory(folder);
|
||||
|
@ -712,6 +726,11 @@ void PresetBundle::update_user_presets_directory(const std::string preset_folder
|
|||
const std::string dir_user_presets = data_dir() + "/" + PRESET_USER_DIR + "/"+ preset_folder;
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" enter, update directory to %1%")%dir_user_presets;
|
||||
|
||||
fs::path user_folder(data_dir() + "/" + PRESET_USER_DIR);
|
||||
if (!fs::exists(user_folder))
|
||||
fs::create_directory(user_folder);
|
||||
|
||||
fs::path folder(dir_user_presets);
|
||||
if (!fs::exists(folder))
|
||||
fs::create_directory(folder);
|
||||
|
@ -726,6 +745,10 @@ void PresetBundle::remove_user_presets_directory(const std::string preset_folder
|
|||
{
|
||||
const std::string dir_user_presets = data_dir() + "/" + PRESET_USER_DIR + "/" + preset_folder;
|
||||
|
||||
if (preset_folder.empty()) {
|
||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": preset_folder is empty, no need to remove directory : %1%") % dir_user_presets;
|
||||
return;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" enter, delete directory : %1%") % dir_user_presets;
|
||||
fs::path folder(dir_user_presets);
|
||||
if (fs::exists(folder)) {
|
||||
|
@ -838,7 +861,7 @@ void PresetBundle::remove_users_preset(AppConfig& config)
|
|||
} else {
|
||||
printers.select_preset_by_name(printer_selected_preset_name, false);
|
||||
}
|
||||
|
||||
|
||||
std::string selected_print_name = prints.get_selected_preset().name;
|
||||
bool need_reset_print_preset = false;
|
||||
// remove preset if user_id is not current user
|
||||
|
@ -1114,14 +1137,33 @@ void PresetBundle::save_changes_for_preset(const std::string& new_name, Preset::
|
|||
|
||||
void PresetBundle::load_installed_filaments(AppConfig &config)
|
||||
{
|
||||
if (! config.has_section(AppConfig::SECTION_FILAMENTS)
|
||||
|| config.get_section(AppConfig::SECTION_FILAMENTS).empty()) {
|
||||
//if (! config.has_section(AppConfig::SECTION_FILAMENTS)
|
||||
// || config.get_section(AppConfig::SECTION_FILAMENTS).empty()) {
|
||||
// Compatibility with the PrusaSlicer 2.1.1 and older, where the filament profiles were not installable yet.
|
||||
// Find all filament profiles, which are compatible with installed printers, and act as if these filament profiles
|
||||
// were installed.
|
||||
std::unordered_set<const Preset*> compatible_filaments;
|
||||
for (const Preset &printer : printers)
|
||||
if (printer.is_visible && printer.printer_technology() == ptFFF && printer.vendor && (!printer.vendor->models.empty())) {
|
||||
bool add_default_materials = true;
|
||||
if (config.has_section(AppConfig::SECTION_FILAMENTS))
|
||||
{
|
||||
const std::map<std::string, std::string>& installed_filament = config.get_section(AppConfig::SECTION_FILAMENTS);
|
||||
for (auto filament_iter : installed_filament)
|
||||
{
|
||||
Preset* filament = filaments.find_preset(filament_iter.first, false, true);
|
||||
if (filament && is_compatible_with_printer(PresetWithVendorProfile(*filament, filament->vendor), PresetWithVendorProfile(printer, printer.vendor)))
|
||||
{
|
||||
//already has compatible filament
|
||||
add_default_materials = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!add_default_materials)
|
||||
continue;
|
||||
|
||||
for (auto default_filament: printer.vendor->models[0].default_materials)
|
||||
{
|
||||
Preset* filament = filaments.find_preset(default_filament, false, true);
|
||||
|
@ -1136,7 +1178,7 @@ void PresetBundle::load_installed_filaments(AppConfig &config)
|
|||
// and mark these filaments as installed, therefore this code will not be executed at the next start of the application.
|
||||
for (const auto &filament: compatible_filaments)
|
||||
config.set(AppConfig::SECTION_FILAMENTS, filament->name, "true");
|
||||
}
|
||||
//}
|
||||
|
||||
for (auto &preset : filaments)
|
||||
preset.set_visible_from_appconfig(config);
|
||||
|
|
|
@ -1738,13 +1738,13 @@ void Print::finalize_first_layer_convex_hull()
|
|||
// Wipe tower support.
|
||||
bool Print::has_wipe_tower() const
|
||||
{
|
||||
if (enable_timelapse_print())
|
||||
return true;
|
||||
if (m_config.enable_prime_tower.value == true) {
|
||||
if (enable_timelapse_print())
|
||||
return true;
|
||||
|
||||
return
|
||||
! m_config.spiral_mode.value &&
|
||||
m_config.enable_prime_tower.value &&
|
||||
m_config.filament_diameter.values.size() > 1;
|
||||
return !m_config.spiral_mode.value && m_config.filament_diameter.values.size() > 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const WipeTowerData& Print::wipe_tower_data(size_t filaments_cnt) const
|
||||
|
|
|
@ -61,6 +61,12 @@ namespace FillAdaptive {
|
|||
using OctreePtr = std::unique_ptr<Octree, OctreeDeleter>;
|
||||
};
|
||||
|
||||
namespace FillLightning {
|
||||
class Generator;
|
||||
struct GeneratorDeleter;
|
||||
using GeneratorPtr = std::unique_ptr<Generator, GeneratorDeleter>;
|
||||
}; // namespace FillLightning
|
||||
|
||||
// Print step IDs for keeping track of the print state.
|
||||
// The Print steps are applied in this order.
|
||||
enum PrintStep {
|
||||
|
@ -466,6 +472,7 @@ private:
|
|||
void combine_infill();
|
||||
void _generate_support_material();
|
||||
std::pair<FillAdaptive::OctreePtr, FillAdaptive::OctreePtr> prepare_adaptive_infill_data();
|
||||
FillLightning::GeneratorPtr prepare_lightning_infill_data();
|
||||
|
||||
// BBS
|
||||
SupportNecessaryType is_support_necessary();
|
||||
|
|
|
@ -180,7 +180,10 @@ CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SlicingMode)
|
|||
static t_config_enum_values s_keys_map_SupportMaterialPattern {
|
||||
{ "rectilinear", smpRectilinear },
|
||||
{ "rectilinear-grid", smpRectilinearGrid },
|
||||
{ "honeycomb", smpHoneycomb }
|
||||
{ "honeycomb", smpHoneycomb },
|
||||
#if HAS_LIGHTNING_INFILL
|
||||
{ "lightning", smpLightning },
|
||||
#endif
|
||||
};
|
||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportMaterialPattern)
|
||||
|
||||
|
@ -282,6 +285,7 @@ static const t_config_enum_values s_keys_map_BedType = {
|
|||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BedType)
|
||||
|
||||
static t_config_enum_values s_keys_map_NozzleType {
|
||||
{ "undefine", int(NozzleType::ntUndefine) },
|
||||
{ "hardened_steel", int(NozzleType::ntHardenedSteel) },
|
||||
{ "stainless_steel",int(NozzleType::ntStainlessSteel) },
|
||||
{ "brass", int(NozzleType::ntBrass) }
|
||||
|
@ -371,9 +375,9 @@ void PrintConfigDef::init_common_params()
|
|||
def = this->add("print_host", coString);
|
||||
def->label = L("Hostname, IP or URL");
|
||||
def->tooltip = L("Slic3r can upload G-code files to a printer host. This field should contain "
|
||||
"the hostname, IP address or URL of the printer host instance. "
|
||||
"Print host behind HAProxy with basic auth enabled can be accessed by putting the user name and password into the URL "
|
||||
"in the following format: https://username:password@your-octopi-address/");
|
||||
"the hostname, IP address or URL of the printer host instance. "
|
||||
"Print host behind HAProxy with basic auth enabled can be accessed by putting the user name and password into the URL "
|
||||
"in the following format: https://username:password@your-octopi-address/");
|
||||
def->mode = comAdvanced;
|
||||
def->cli = ConfigOptionDef::nocli;
|
||||
def->set_default_value(new ConfigOptionString(""));
|
||||
|
@ -381,11 +385,11 @@ void PrintConfigDef::init_common_params()
|
|||
def = this->add("printhost_apikey", coString);
|
||||
def->label = L("API Key / Password");
|
||||
def->tooltip = L("Slic3r can upload G-code files to a printer host. This field should contain "
|
||||
"the API Key or the password required for authentication.");
|
||||
"the API Key or the password required for authentication.");
|
||||
def->mode = comAdvanced;
|
||||
def->cli = ConfigOptionDef::nocli;
|
||||
def->set_default_value(new ConfigOptionString(""));
|
||||
|
||||
|
||||
def = this->add("printhost_port", coString);
|
||||
def->label = L("Printer");
|
||||
def->tooltip = L("Name of the printer");
|
||||
|
@ -393,27 +397,27 @@ void PrintConfigDef::init_common_params()
|
|||
def->mode = comAdvanced;
|
||||
def->cli = ConfigOptionDef::nocli;
|
||||
def->set_default_value(new ConfigOptionString(""));
|
||||
|
||||
|
||||
def = this->add("printhost_cafile", coString);
|
||||
def->label = L("HTTPS CA File");
|
||||
def->tooltip = L("Custom CA certificate file can be specified for HTTPS OctoPrint connections, in crt/pem format. "
|
||||
"If left blank, the default OS CA certificate repository is used.");
|
||||
"If left blank, the default OS CA certificate repository is used.");
|
||||
def->mode = comAdvanced;
|
||||
def->cli = ConfigOptionDef::nocli;
|
||||
def->set_default_value(new ConfigOptionString(""));
|
||||
|
||||
|
||||
// Options used by physical printers
|
||||
|
||||
|
||||
def = this->add("printhost_user", coString);
|
||||
def->label = L("User");
|
||||
// def->tooltip = L("");
|
||||
// def->tooltip = L("");
|
||||
def->mode = comAdvanced;
|
||||
def->cli = ConfigOptionDef::nocli;
|
||||
def->set_default_value(new ConfigOptionString(""));
|
||||
|
||||
|
||||
def = this->add("printhost_password", coString);
|
||||
def->label = L("Password");
|
||||
// def->tooltip = L("");
|
||||
// def->tooltip = L("");
|
||||
def->mode = comAdvanced;
|
||||
def->cli = ConfigOptionDef::nocli;
|
||||
def->set_default_value(new ConfigOptionString(""));
|
||||
|
@ -422,11 +426,11 @@ void PrintConfigDef::init_common_params()
|
|||
def = this->add("printhost_ssl_ignore_revoke", coBool);
|
||||
def->label = L("Ignore HTTPS certificate revocation checks");
|
||||
def->tooltip = L("Ignore HTTPS certificate revocation checks in case of missing or offline distribution points. "
|
||||
"One may want to enable this option for self signed certificates if connection fails.");
|
||||
"One may want to enable this option for self signed certificates if connection fails.");
|
||||
def->mode = comAdvanced;
|
||||
def->cli = ConfigOptionDef::nocli;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
|
||||
def = this->add("preset_names", coStrings);
|
||||
def->label = L("Printer preset names");
|
||||
def->tooltip = L("Names of presets related to the physical printer");
|
||||
|
@ -435,7 +439,7 @@ void PrintConfigDef::init_common_params()
|
|||
|
||||
def = this->add("printhost_authorization_type", coEnum);
|
||||
def->label = L("Authorization Type");
|
||||
// def->tooltip = L("");
|
||||
// def->tooltip = L("");
|
||||
def->enum_keys_map = &ConfigOptionEnum<AuthorizationType>::get_enum_values();
|
||||
def->enum_values.push_back("key");
|
||||
def->enum_values.push_back("user");
|
||||
|
@ -460,22 +464,23 @@ void PrintConfigDef::init_fff_params()
|
|||
const int max_temp = 1500;
|
||||
|
||||
def = this->add("reduce_crossing_wall", coBool);
|
||||
def->label = L("Avoid crossing wall when travel");
|
||||
def->label = L("Avoid crossing wall");
|
||||
def->category = L("Quality");
|
||||
def->tooltip = L("Detour and avoid to travel across wall which may cause blob on surface");
|
||||
def->mode = comDevelop;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("max_travel_detour_distance", coFloat);
|
||||
def->label = L("Max travel detour distance");
|
||||
def = this->add("max_travel_detour_distance", coFloatOrPercent);
|
||||
def->label = L("Avoid crossing wall - Max detour length");
|
||||
def->category = L("Quality");
|
||||
def->tooltip = L("Maximum detour distance for avoiding crossing wall. "
|
||||
"Don't detour if the detour distance is large than this value");
|
||||
def->sidetext = L("mm");
|
||||
"Don't detour if the detour distance is large than this value. "
|
||||
"Detour length could be specified either as an absolute value or as percentage (for example 50%) of a direct travel path. Zero to disable");
|
||||
def->sidetext = L("mm or %");
|
||||
def->min = 0;
|
||||
def->max_literal = 1000;
|
||||
def->mode = comDevelop;
|
||||
def->set_default_value(new ConfigOptionFloat(0.));
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloatOrPercent(0., false));
|
||||
|
||||
// BBS
|
||||
def = this->add("cool_plate_temp", coInts);
|
||||
|
@ -590,7 +595,7 @@ void PrintConfigDef::init_fff_params()
|
|||
"than bottom shell thickness, the bottom shell layers will be increased");
|
||||
def->full_label = L("Bottom shell layers");
|
||||
def->min = 0;
|
||||
def->set_default_value(new ConfigOptionInt(2));
|
||||
def->set_default_value(new ConfigOptionInt(3));
|
||||
|
||||
def = this->add("bottom_shell_thickness", coFloat);
|
||||
def->label = L("Bottom shell thickness");
|
||||
|
@ -672,7 +677,7 @@ void PrintConfigDef::init_fff_params()
|
|||
def->label = L("Only one wall on top surfaces");
|
||||
def->category = L("Quality");
|
||||
def->tooltip = L("Use only one wall on flat top surface, to give more space to the top infill pattern");
|
||||
def->set_default_value(new ConfigOptionBool(true));
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("only_one_wall_first_layer", coBool);
|
||||
def->label = L("Only one wall on first layer");
|
||||
|
@ -736,7 +741,7 @@ void PrintConfigDef::init_fff_params()
|
|||
def->sidetext = L("mm/s");
|
||||
def->min = 0;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(60));
|
||||
def->set_default_value(new ConfigOptionFloat(25));
|
||||
|
||||
def = this->add("brim_width", coFloat);
|
||||
def->label = L("Brim width");
|
||||
|
@ -846,7 +851,7 @@ void PrintConfigDef::init_fff_params()
|
|||
def->sidetext = L("mm/s²");
|
||||
def->min = 0;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(5000));
|
||||
def->set_default_value(new ConfigOptionFloat(500.0));
|
||||
|
||||
def = this->add("default_filament_profile", coStrings);
|
||||
def->label = L("Default filament profile");
|
||||
|
@ -893,7 +898,7 @@ void PrintConfigDef::init_fff_params()
|
|||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(20));
|
||||
def->set_default_value(new ConfigOptionFloat(10));
|
||||
|
||||
def = this->add("machine_end_gcode", coString);
|
||||
def->label = L("End G-code");
|
||||
|
@ -902,7 +907,7 @@ void PrintConfigDef::init_fff_params()
|
|||
def->full_width = true;
|
||||
def->height = 12;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionString("M104 S0\n"));
|
||||
def->set_default_value(new ConfigOptionString("M104 S0 ; turn off temperature\nG28 X0 ; home X axis\nM84 ; disable motors\n"));
|
||||
|
||||
def = this->add("filament_end_gcode", coStrings);
|
||||
def->label = L("End G-code");
|
||||
|
@ -962,7 +967,7 @@ void PrintConfigDef::init_fff_params()
|
|||
def->sidetext = L("mm/s");
|
||||
def->min = 0;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(120));
|
||||
def->set_default_value(new ConfigOptionFloat(60));
|
||||
|
||||
def = this->add("wall_infill_order", coEnum);
|
||||
def->label = L("Order of inner wall/outer wall/infil");
|
||||
|
@ -997,29 +1002,29 @@ void PrintConfigDef::init_fff_params()
|
|||
|
||||
def = this->add("extruder_clearance_height_to_rod", coFloat);
|
||||
def->label = L("Height to rod");
|
||||
def->tooltip = L("Height of the clearance cylinder around extruder. "
|
||||
"Used as input of auto-arrange to avoid collision when print object by object");
|
||||
def->tooltip = L("Distance of the nozzle tip to the lower rod. "
|
||||
"Used as input of auto-arranging to avoid collision when printing by object");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->mode = comDevelop;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(40));
|
||||
|
||||
// BBS
|
||||
def = this->add("extruder_clearance_height_to_lid", coFloat);
|
||||
def->label = L("Height to lid");
|
||||
def->tooltip = L("Height of the clearance cylinder around extruder. "
|
||||
"Used as input of auto-arrange to avoid collision when print object by object");
|
||||
def->tooltip = L("Distance of the nozzle tip to the lid. "
|
||||
"Used as input of auto-arranging to avoid collision when printing by object");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->mode = comDevelop;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(120));
|
||||
|
||||
def = this->add("extruder_clearance_radius", coFloat);
|
||||
def->label = L("Radius");
|
||||
def->tooltip = L("Clearance radius around extruder. Used as input of auto-arrange to avoid collision when print object by object");
|
||||
def->tooltip = L("Clearance radius around extruder. Used as input of auto-arranging to avoid collision when printing by object");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->mode = comDevelop;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(40));
|
||||
|
||||
def = this->add("extruder_colour", coStrings);
|
||||
|
@ -1257,7 +1262,7 @@ void PrintConfigDef::init_fff_params()
|
|||
//def->enum_values.push_back("octagramspiral");
|
||||
//def->enum_values.push_back("supportcubic");
|
||||
#if HAS_LIGHTNING_INFILL
|
||||
//def->enum_values.push_back("lightning");
|
||||
def->enum_values.push_back("lightning");
|
||||
#endif // HAS_LIGHTNING_INFILL
|
||||
def->enum_labels.push_back(L("Concentric"));
|
||||
def->enum_labels.push_back(L("Rectilinear"));
|
||||
|
@ -1276,7 +1281,7 @@ void PrintConfigDef::init_fff_params()
|
|||
//def->enum_labels.push_back(L("Octagram Spiral"));
|
||||
//def->enum_labels.push_back(L("Support Cubic"));
|
||||
#if HAS_LIGHTNING_INFILL
|
||||
//def->enum_labels.push_back(L("Lightning"));
|
||||
def->enum_labels.push_back(L("Lightning"));
|
||||
#endif // HAS_LIGHTNING_INFILL
|
||||
def->set_default_value(new ConfigOptionEnum<InfillPattern>(ipCubic));
|
||||
|
||||
|
@ -1310,7 +1315,7 @@ void PrintConfigDef::init_fff_params()
|
|||
def->sidetext = L("mm/s²");
|
||||
def->min = 0;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(2000));
|
||||
def->set_default_value(new ConfigOptionFloat(500));
|
||||
|
||||
def = this->add("initial_layer_acceleration", coFloat);
|
||||
def->label = L("Initial layer");
|
||||
|
@ -1502,20 +1507,22 @@ void PrintConfigDef::init_fff_params()
|
|||
def->tooltip = L("The metallic material of nozzle. This determines the abrasive resistance of nozzle, and "
|
||||
"what kind of filament can be printed");
|
||||
def->enum_keys_map = &ConfigOptionEnum<NozzleType>::get_enum_values();
|
||||
def->enum_values.push_back("undefine");
|
||||
def->enum_values.push_back("hardened_steel");
|
||||
def->enum_values.push_back("stainless_steel");
|
||||
def->enum_values.push_back("brass");
|
||||
def->enum_labels.push_back(L("Undefine"));
|
||||
def->enum_labels.push_back(L("Hardened steel"));
|
||||
def->enum_labels.push_back(L("Stainless steel"));
|
||||
def->enum_labels.push_back(L("Brass"));
|
||||
def->mode = comSimple;
|
||||
def->set_default_value(new ConfigOptionEnum<NozzleType>(ntHardenedSteel));
|
||||
def->mode = comDevelop;
|
||||
def->set_default_value(new ConfigOptionEnum<NozzleType>(ntUndefine));
|
||||
|
||||
def = this->add("auxiliary_fan", coBool);
|
||||
def->label = L("Auxiliary part cooling fan");
|
||||
def->tooltip = L("Enable this option if machine has auxiliary part cooling fan");
|
||||
def->mode = comSimple;
|
||||
def->set_default_value(new ConfigOptionBool(true));
|
||||
def->mode = comDevelop;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("gcode_flavor", coEnum);
|
||||
def->label = L("G-code flavor");
|
||||
|
@ -1533,7 +1540,7 @@ void PrintConfigDef::init_fff_params()
|
|||
//def->enum_values.push_back("machinekit");
|
||||
//def->enum_values.push_back("smoothie");
|
||||
//def->enum_values.push_back("no-extrusion");
|
||||
def->enum_labels.push_back("Marlin");
|
||||
def->enum_labels.push_back("Marlin(legacy)");
|
||||
//def->enum_labels.push_back("RepRap/Sprinter");
|
||||
//def->enum_labels.push_back("RepRapFirmware");
|
||||
//def->enum_labels.push_back("Repetier");
|
||||
|
@ -1592,7 +1599,7 @@ void PrintConfigDef::init_fff_params()
|
|||
def->sidetext = L("mm/s");
|
||||
def->min = 0;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(150));
|
||||
def->set_default_value(new ConfigOptionFloat(100));
|
||||
|
||||
def = this->add("inherits", coString);
|
||||
//def->label = L("Inherits profile");
|
||||
|
@ -1685,7 +1692,16 @@ void PrintConfigDef::init_fff_params()
|
|||
|
||||
def = this->add("machine_pause_gcode", coString);
|
||||
def->label = L("Pause G-code");
|
||||
//def->tooltip = L("This G-code will be used as a code for the pause print");
|
||||
def->tooltip = L("This G-code will be used as a code for the pause print. User can insert pause G-code in gcode viewer");
|
||||
def->multiline = true;
|
||||
def->full_width = true;
|
||||
def->height = 12;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionString(""));
|
||||
|
||||
def = this->add("template_custom_gcode", coString);
|
||||
def->label = L("Custom G-code");
|
||||
def->tooltip = L("This G-code will be used as a custom code");
|
||||
def->multiline = true;
|
||||
def->full_width = true;
|
||||
def->height = 12;
|
||||
|
@ -1701,10 +1717,10 @@ void PrintConfigDef::init_fff_params()
|
|||
};
|
||||
std::vector<AxisDefault> axes {
|
||||
// name, max_feedrate, max_acceleration, max_jerk
|
||||
{ "x", { 500., 200. }, { 9000., 1000. }, { 10. , 10. } },
|
||||
{ "y", { 500., 200. }, { 9000., 1000. }, { 10. , 10. } },
|
||||
{ "x", { 500., 200. }, { 1000., 1000. }, { 10. , 10. } },
|
||||
{ "y", { 500., 200. }, { 1000., 1000. }, { 10. , 10. } },
|
||||
{ "z", { 12., 12. }, { 500., 200. }, { 0.2, 0.4 } },
|
||||
{ "e", { 120., 120. }, { 10000., 5000. }, { 2.5, 2.5 } }
|
||||
{ "e", { 120., 120. }, { 5000., 5000. }, { 2.5, 2.5 } }
|
||||
};
|
||||
for (const AxisDefault &axis : axes) {
|
||||
std::string axis_upper = boost::to_upper_copy<std::string>(axis.name);
|
||||
|
@ -1795,7 +1811,7 @@ void PrintConfigDef::init_fff_params()
|
|||
def->sidetext = L("mm/s²");
|
||||
def->min = 0;
|
||||
def->readonly = false;
|
||||
def->mode = comDevelop;
|
||||
def->mode = comSimple;
|
||||
def->set_default_value(new ConfigOptionFloats{ 1500., 1250. });
|
||||
|
||||
|
||||
|
@ -1807,7 +1823,7 @@ void PrintConfigDef::init_fff_params()
|
|||
def->sidetext = L("mm/s²");
|
||||
def->min = 0;
|
||||
def->readonly = false;
|
||||
def->mode = comDevelop;
|
||||
def->mode = comSimple;
|
||||
def->set_default_value(new ConfigOptionFloats{ 1500., 1250. });
|
||||
|
||||
// M204 T... [mm/sec^2]
|
||||
|
@ -1937,7 +1953,7 @@ void PrintConfigDef::init_fff_params()
|
|||
def->sidetext = L("mm³");
|
||||
def->mode = comDevelop;
|
||||
def->readonly = true;
|
||||
def->set_default_value(new ConfigOptionFloat { 118 });
|
||||
def->set_default_value(new ConfigOptionFloat { 0.0 });
|
||||
|
||||
def = this->add("reduce_infill_retraction", coBool);
|
||||
def->label = L("Reduce infill retraction");
|
||||
|
@ -1945,7 +1961,7 @@ void PrintConfigDef::init_fff_params()
|
|||
"This can reduce times of retraction for complex model and save printing time, but make slicing and "
|
||||
"G-code generating slower");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionBool(true));
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("ooze_prevention", coBool);
|
||||
def->label = L("Enable");
|
||||
|
@ -2251,7 +2267,7 @@ void PrintConfigDef::init_fff_params()
|
|||
def->sidetext = L("mm²");
|
||||
def->min = 0;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(5));
|
||||
def->set_default_value(new ConfigOptionFloat(15));
|
||||
|
||||
def = this->add("solid_infill_filament", coInt);
|
||||
//def->label = L("Solid infill");
|
||||
|
@ -2327,7 +2343,7 @@ void PrintConfigDef::init_fff_params()
|
|||
def->full_width = true;
|
||||
def->height = 12;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionString("G28\nG1 Z10 F100\n"));
|
||||
def->set_default_value(new ConfigOptionString("G28 ; home all axes\nG1 Z5 F5000 ; lift nozzle\n"));
|
||||
|
||||
def = this->add("filament_start_gcode", coStrings);
|
||||
def->label = L("Start G-code");
|
||||
|
@ -2532,7 +2548,7 @@ void PrintConfigDef::init_fff_params()
|
|||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(0));
|
||||
def->set_default_value(new ConfigOptionFloat(0.5));
|
||||
|
||||
//BBS
|
||||
def = this->add("support_bottom_interface_spacing", coFloat);
|
||||
|
@ -2561,9 +2577,15 @@ void PrintConfigDef::init_fff_params()
|
|||
def->enum_values.push_back("rectilinear");
|
||||
def->enum_values.push_back("rectilinear-grid");
|
||||
def->enum_values.push_back("honeycomb");
|
||||
#if HAS_LIGHTNING_INFILL
|
||||
def->enum_values.push_back("lightning");
|
||||
#endif
|
||||
def->enum_labels.push_back(L("Rectilinear"));
|
||||
def->enum_labels.push_back(L("Rectilinear grid"));
|
||||
def->enum_labels.push_back(L("Honeycomb"));
|
||||
#if HAS_LIGHTNING_INFILL
|
||||
def->enum_labels.push_back(L("Lightning"));
|
||||
#endif
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionEnum<SupportMaterialPattern>(smpRectilinear));
|
||||
|
||||
|
@ -2765,7 +2787,7 @@ void PrintConfigDef::init_fff_params()
|
|||
"than top shell thickness, the top shell layers will be increased");
|
||||
def->full_label = L("Top solid layers");
|
||||
def->min = 0;
|
||||
def->set_default_value(new ConfigOptionInt(2));
|
||||
def->set_default_value(new ConfigOptionInt(4));
|
||||
|
||||
def = this->add("top_shell_thickness", coFloat);
|
||||
def->label = L("Top shell thickness");
|
||||
|
@ -2784,7 +2806,7 @@ void PrintConfigDef::init_fff_params()
|
|||
def->sidetext = L("mm/s");
|
||||
def->min = 1;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(200));
|
||||
def->set_default_value(new ConfigOptionFloat(120));
|
||||
|
||||
def = this->add("travel_speed_z", coFloat);
|
||||
//def->label = L("Z travel");
|
||||
|
@ -3869,10 +3891,7 @@ void DynamicPrintConfig::normalize_fdm(int used_filaments)
|
|||
|
||||
ConfigOptionEnum<TimelapseType>* timelapse_opt = this->option<ConfigOptionEnum<TimelapseType>>("timelapse_type");
|
||||
bool is_smooth_timelapse = timelapse_opt != nullptr && timelapse_opt->value == TimelapseType::tlSmooth;
|
||||
if (is_smooth_timelapse) {
|
||||
ept_opt->value = true;
|
||||
}
|
||||
else if (used_filaments == 1 || ps_opt->value == PrintSequence::ByObject) {
|
||||
if (!is_smooth_timelapse && (used_filaments == 1 || ps_opt->value == PrintSequence::ByObject)) {
|
||||
ept_opt->value = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ enum AuthorizationType {
|
|||
atKeyPassword, atUserPassword
|
||||
};
|
||||
|
||||
#define HAS_LIGHTNING_INFILL 0
|
||||
#define HAS_LIGHTNING_INFILL 1
|
||||
|
||||
enum InfillPattern : int {
|
||||
ipConcentric, ipRectilinear, ipGrid, ipLine, ipCubic, ipTriangles, ipStars, ipGyroid, ipHoneycomb, ipAdaptiveCubic, ipMonotonic, ipMonotonicLine, ipAlignedRectilinear, ip3DHoneycomb,
|
||||
|
@ -96,6 +96,9 @@ enum class SlicingMode
|
|||
|
||||
enum SupportMaterialPattern {
|
||||
smpRectilinear, smpRectilinearGrid, smpHoneycomb,
|
||||
#if HAS_LIGHTNING_INFILL
|
||||
smpLightning,
|
||||
#endif // HAS_LIGHTNING_INFILL
|
||||
};
|
||||
|
||||
enum SupportMaterialStyle {
|
||||
|
@ -178,7 +181,8 @@ enum BedType {
|
|||
|
||||
// BBS
|
||||
enum NozzleType {
|
||||
ntHardenedSteel = 0,
|
||||
ntUndefine = 0,
|
||||
ntHardenedSteel,
|
||||
ntStainlessSteel,
|
||||
ntBrass,
|
||||
ntCount
|
||||
|
@ -794,6 +798,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
|||
((ConfigOptionFloat, travel_speed_z))
|
||||
((ConfigOptionBool, silent_mode))
|
||||
((ConfigOptionString, machine_pause_gcode))
|
||||
((ConfigOptionString, template_custom_gcode))
|
||||
//BBS
|
||||
((ConfigOptionEnum<NozzleType>, nozzle_type))
|
||||
((ConfigOptionBool, auxiliary_fan))
|
||||
|
@ -807,7 +812,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
|
|||
//BBS
|
||||
((ConfigOptionInts, additional_cooling_fan_speed))
|
||||
((ConfigOptionBool, reduce_crossing_wall))
|
||||
((ConfigOptionFloat, max_travel_detour_distance))
|
||||
((ConfigOptionFloatOrPercent, max_travel_detour_distance))
|
||||
((ConfigOptionPoints, printable_area))
|
||||
//BBS: add bed_exclude_area
|
||||
((ConfigOptionPoints, bed_exclude_area))
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include "TriangleMeshSlicer.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "Fill/FillAdaptive.hpp"
|
||||
#include "Fill/FillLightning.hpp"
|
||||
#include "Format/STL.hpp"
|
||||
#include "InternalBridgeDetector.hpp"
|
||||
#include "TreeSupport.hpp"
|
||||
|
@ -367,14 +368,16 @@ void PrintObject::infill()
|
|||
m_print->set_status(35, L("Generating infill toolpath"));
|
||||
|
||||
auto [adaptive_fill_octree, support_fill_octree] = this->prepare_adaptive_infill_data();
|
||||
auto lightning_generator = this->prepare_lightning_infill_data();
|
||||
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start";
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, m_layers.size()),
|
||||
[this, &adaptive_fill_octree = adaptive_fill_octree, &support_fill_octree = support_fill_octree](const tbb::blocked_range<size_t>& range) {
|
||||
[this, &adaptive_fill_octree = adaptive_fill_octree, &support_fill_octree = support_fill_octree, &lightning_generator](const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
|
||||
m_print->throw_if_canceled();
|
||||
m_layers[layer_idx]->make_fills(adaptive_fill_octree.get(), support_fill_octree.get());
|
||||
m_layers[layer_idx]->make_fills(adaptive_fill_octree.get(), support_fill_octree.get(), lightning_generator.get());
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -534,6 +537,18 @@ std::pair<FillAdaptive::OctreePtr, FillAdaptive::OctreePtr> PrintObject::prepare
|
|||
support_line_spacing ? build_octree(mesh, overhangs.front(), support_line_spacing, true) : OctreePtr());
|
||||
}
|
||||
|
||||
FillLightning::GeneratorPtr PrintObject::prepare_lightning_infill_data()
|
||||
{
|
||||
bool has_lightning_infill = false;
|
||||
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id)
|
||||
if (const PrintRegionConfig& config = this->printing_region(region_id).config(); config.sparse_infill_density > 0 && config.sparse_infill_pattern == ipLightning) {
|
||||
has_lightning_infill = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return has_lightning_infill ? FillLightning::build_generator(std::as_const(*this)) : FillLightning::GeneratorPtr();
|
||||
}
|
||||
|
||||
void PrintObject::clear_layers()
|
||||
{
|
||||
if (!m_shared_object) {
|
||||
|
@ -759,6 +774,17 @@ bool PrintObject::invalidate_state_by_config_options(
|
|||
|| opt_key == "top_surface_line_width"
|
||||
|| opt_key == "initial_layer_line_width") {
|
||||
steps.emplace_back(posInfill);
|
||||
} else if (opt_key == "sparse_infill_pattern") {
|
||||
steps.emplace_back(posInfill);
|
||||
#if HAS_LIGHTNING_INFILL
|
||||
const auto *old_fill_pattern = old_config.option<ConfigOptionEnum<InfillPattern>>(opt_key);
|
||||
const auto *new_fill_pattern = new_config.option<ConfigOptionEnum<InfillPattern>>(opt_key);
|
||||
assert(old_fill_pattern && new_fill_pattern);
|
||||
// We need to recalculate infill surfaces when infill_only_where_needed is enabled, and we are switching from
|
||||
// the Lightning infill to another infill or vice versa.
|
||||
if (PrintObject::infill_only_where_needed && (new_fill_pattern->value == ipLightning || old_fill_pattern->value == ipLightning))
|
||||
steps.emplace_back(posPrepareInfill);
|
||||
#endif
|
||||
} else if (opt_key == "sparse_infill_density") {
|
||||
// One likely wants to reslice only when switching between zero infill to simulate boolean difference (subtracting volumes),
|
||||
// normal infill and 100% (solid) infill.
|
||||
|
@ -781,6 +807,8 @@ bool PrintObject::invalidate_state_by_config_options(
|
|||
|| opt_key == "fuzzy_skin_thickness"
|
||||
|| opt_key == "fuzzy_skin_point_distance"
|
||||
|| opt_key == "detect_overhang_wall"
|
||||
//BBS
|
||||
|| opt_key == "enable_overhang_speed"
|
||||
|| opt_key == "detect_thin_wall") {
|
||||
steps.emplace_back(posPerimeters);
|
||||
steps.emplace_back(posSupportMaterial);
|
||||
|
@ -796,8 +824,6 @@ bool PrintObject::invalidate_state_by_config_options(
|
|||
opt_key == "seam_position"
|
||||
|| opt_key == "support_speed"
|
||||
|| opt_key == "support_interface_speed"
|
||||
//BBS
|
||||
|| opt_key == "enable_overhang_speed"
|
||||
|| opt_key == "overhang_1_4_speed"
|
||||
|| opt_key == "overhang_2_4_speed"
|
||||
|| opt_key == "overhang_3_4_speed"
|
||||
|
|
|
@ -38,6 +38,7 @@ struct FilamentInfo
|
|||
int id; // filament id = extruder id, start with 0.
|
||||
std::string type;
|
||||
std::string color;
|
||||
std::string filament_id;
|
||||
float used_m;
|
||||
float used_g;
|
||||
int tray_id; // start with 0
|
||||
|
|
|
@ -1513,7 +1513,7 @@ static inline Polygons detect_overhangs(
|
|||
const coordf_t max_bridge_length = scale_(object_config.max_bridge_length.value);
|
||||
const bool bridge_no_support = object_config.bridge_no_support.value;
|
||||
|
||||
if (layer_id == 0)
|
||||
if (layer_id == 0)
|
||||
{
|
||||
// Don't fill in the holes. The user may apply a higher raft_expansion if one wants a better 1st layer adhesion.
|
||||
overhang_polygons = to_polygons(layer.lslices);
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
#include "I18N.hpp"
|
||||
#include <libnest2d/backends/libslic3r/geometries.hpp>
|
||||
|
||||
#include "Fill/FillBase.hpp"
|
||||
|
||||
#define _L(s) Slic3r::I18N::translate(s)
|
||||
|
||||
|
||||
|
@ -283,6 +285,49 @@ static void draw_layer_mst
|
|||
svg.draw(lines, "blue", coord_t(scale_(0.05)));
|
||||
svg.draw_outline(outline, "yellow");
|
||||
}
|
||||
|
||||
static void draw_two_overhangs_to_svg(TreeSupportLayer* ts_layer, const ExPolygons& overhangs1, const ExPolygons& overhangs2)
|
||||
{
|
||||
if (overhangs1.empty() && overhangs2.empty())
|
||||
return;
|
||||
BoundingBox bbox1 = get_extents(overhangs1);
|
||||
BoundingBox bbox2 = get_extents(overhangs2);
|
||||
bbox1.merge(bbox2);
|
||||
|
||||
SVG svg(get_svg_filename(std::to_string(ts_layer->print_z), "two_overhangs"), bbox1);
|
||||
if (!svg.is_opened()) return;
|
||||
|
||||
svg.draw(union_ex(overhangs1), "blue");
|
||||
svg.draw(union_ex(overhangs2), "red");
|
||||
}
|
||||
|
||||
static void draw_polylines(TreeSupportLayer* ts_layer, Polylines& polylines)
|
||||
{
|
||||
if (polylines.empty())
|
||||
return;
|
||||
BoundingBox bbox = get_extents(polylines);
|
||||
|
||||
SVG svg(get_svg_filename(std::to_string(ts_layer->print_z), "lightnings"), bbox);
|
||||
if (!svg.is_opened()) return;
|
||||
|
||||
int id = 0;
|
||||
for (Polyline& pline : polylines)
|
||||
{
|
||||
int i1, i2;
|
||||
for (size_t i = 0; i < pline.size() - 1; i++)
|
||||
{
|
||||
i1 = i;
|
||||
i2 = i + 1;
|
||||
svg.draw(Line(pline.points[i1], pline.points[i2]), "blue");
|
||||
svg.draw(pline.points[i1], "red");
|
||||
id++;
|
||||
svg.draw_text(pline.points[i1], std::to_string(id).c_str(), "black", 1);
|
||||
}
|
||||
svg.draw(pline.points[i2], "red");
|
||||
id++;
|
||||
svg.draw_text(pline.points[i2], std::to_string(id).c_str(), "black", 1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Move point from inside polygon if distance>0, outside if distance<0.
|
||||
|
@ -641,7 +686,11 @@ TreeSupport::TreeSupport(PrintObject& object, const SlicingParameters &slicing_p
|
|||
m_raft_layers = slicing_params.base_raft_layers + slicing_params.interface_raft_layers;
|
||||
|
||||
SupportMaterialPattern support_pattern = m_object_config->support_base_pattern;
|
||||
m_support_params.base_fill_pattern = support_pattern == smpHoneycomb ? ipHoneycomb :
|
||||
m_support_params.base_fill_pattern =
|
||||
#if HAS_LIGHTNING_INFILL
|
||||
support_pattern == smpLightning ? ipLightning :
|
||||
#endif
|
||||
support_pattern == smpHoneycomb ? ipHoneycomb :
|
||||
m_support_params.support_density > 0.95 || m_support_params.with_sheath ? ipRectilinear :
|
||||
ipSupportBase;
|
||||
m_support_params.interface_fill_pattern = (m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase);
|
||||
|
@ -659,7 +708,7 @@ void TreeSupport::detect_object_overhangs()
|
|||
if (m_object->tree_support_layer_count() >= m_object->layer_count())
|
||||
return;
|
||||
|
||||
// Create Tree Support Layers
|
||||
// Clear and create Tree Support Layers
|
||||
m_object->clear_tree_support_layers();
|
||||
m_object->clear_tree_support_preview_cache();
|
||||
|
||||
|
@ -672,11 +721,11 @@ void TreeSupport::detect_object_overhangs()
|
|||
const coordf_t extrusion_width = config.line_width.value;
|
||||
const coordf_t extrusion_width_scaled = scale_(extrusion_width);
|
||||
const coordf_t max_bridge_length = scale_(config.max_bridge_length.value);
|
||||
const bool bridge_no_support = max_bridge_length > 0;// config.bridge_no_support.value;
|
||||
const bool bridge_no_support = max_bridge_length > 0;
|
||||
const bool support_critical_regions_only = config.support_critical_regions_only.value;
|
||||
const int enforce_support_layers = config.enforce_support_layers.value;
|
||||
const double area_thresh_well_supported = SQ(scale_(6)); // min: 6x6=36mm^2
|
||||
const double length_thresh_well_supported = scale_(6); // min: 6mm
|
||||
const double area_thresh_well_supported = SQ(scale_(6));
|
||||
const double length_thresh_well_supported = scale_(6);
|
||||
static const double sharp_tail_max_support_height = 8.f;
|
||||
// a region is considered well supported if the number of layers below it exceeds this threshold
|
||||
const int thresh_layers_below = 10 / config.layer_height;
|
||||
|
@ -773,20 +822,19 @@ void TreeSupport::detect_object_overhangs()
|
|||
region2clusterInd.emplace(®ion, regionClusters.size() - 1);
|
||||
}
|
||||
};
|
||||
|
||||
// main part of sharptail detections
|
||||
has_sharp_tail = false;
|
||||
if (std::set<SupportType>{stTreeAuto, stHybridAuto, stTree}.count(stype))// == stTreeAuto || stype == stHybridAuto || stype == stTree)
|
||||
{
|
||||
double threshold_rad = (config.support_threshold_angle.value < EPSILON ? 30 : config.support_threshold_angle.value+1) * M_PI / 180.;
|
||||
ExPolygons regions_well_supported; // regions on buildplate or well supported
|
||||
std::map<ExPolygon, int, ExPolygonComp> region_layers_below; // regions and the number of layers below
|
||||
ExPolygons lower_overhang_dilated; // for small overhang
|
||||
|
||||
for (size_t layer_nr = 0; layer_nr < m_object->layer_count(); layer_nr++)
|
||||
{
|
||||
ExPolygons regions_well_supported;
|
||||
std::map<ExPolygon, int, ExPolygonComp> region_layers_below;
|
||||
ExPolygons lower_overhang_dilated;
|
||||
|
||||
for (size_t layer_nr = 0; layer_nr < m_object->layer_count(); layer_nr++){
|
||||
if (m_object->print()->canceled())
|
||||
break;
|
||||
|
||||
|
||||
if (!is_auto && layer_nr > enforce_support_layers)
|
||||
continue;
|
||||
|
||||
|
@ -824,7 +872,7 @@ void TreeSupport::detect_object_overhangs()
|
|||
// normal overhang
|
||||
ExPolygons lower_layer_offseted = offset_ex(lower_polys, support_offset_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS);
|
||||
ExPolygons overhang_areas = std::move(diff_ex(curr_polys, lower_layer_offseted));
|
||||
// overhang_areas = std::move(offset2_ex(overhang_areas, -0.1 * extrusion_width_scaled, 0.1 * extrusion_width_scaled));
|
||||
|
||||
overhang_areas.erase(std::remove_if(overhang_areas.begin(), overhang_areas.end(),
|
||||
[extrusion_width_scaled](ExPolygon &area) { return offset_ex(area, -0.1 * extrusion_width_scaled).empty(); }),
|
||||
overhang_areas.end());
|
||||
|
@ -912,7 +960,7 @@ void TreeSupport::detect_object_overhangs()
|
|||
|
||||
// 2.4 if the area grows fast than threshold, it get connected to other part or
|
||||
// it has a sharp slop and will be auto supported.
|
||||
ExPolygons new_overhang_expolys = diff_ex({ expoly }, lower_layer_sharptails);
|
||||
ExPolygons new_overhang_expolys = diff_ex({expoly}, lower_layer_sharptails);
|
||||
if (!offset_ex(new_overhang_expolys, -5.0 * extrusion_width_scaled).empty()) {
|
||||
is_sharp_tail = false;
|
||||
break;
|
||||
|
@ -1084,7 +1132,6 @@ void TreeSupport::detect_object_overhangs()
|
|||
// if (erode1.empty() && !inter_with_others.empty())
|
||||
// blockers[layer_nr].push_back(p_overhang->contour);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1107,12 +1154,13 @@ void TreeSupport::detect_object_overhangs()
|
|||
for (auto &area : ts_layer->overhang_areas) {
|
||||
ts_layer->overhang_types.emplace(&area, TreeSupportLayer::Detected);
|
||||
}
|
||||
|
||||
// enforcers
|
||||
if (layer_nr < enforcers.size()) {
|
||||
Polygons& enforcer = enforcers[layer_nr];
|
||||
// coconut: enforcer can't do offset2_ex, otherwise faces with angle near 90 degrees can't have enforcers, which
|
||||
// is not good. For example: tails of animals needs extra support except the lowest tip.
|
||||
//enforcer = std::move(offset2_ex(enforcer, -0.1 * extrusion_width_scaled, 0.1 * extrusion_width_scaled));
|
||||
enforcer = offset(enforcer, 0.1 * extrusion_width_scaled);
|
||||
for (const Polygon& poly : enforcer) {
|
||||
ts_layer->overhang_areas.emplace_back(poly);
|
||||
ts_layer->overhang_types.emplace(&ts_layer->overhang_areas.back(), TreeSupportLayer::Enforced);
|
||||
|
@ -1507,7 +1555,7 @@ void TreeSupport::generate_toolpaths()
|
|||
ExtrusionRole role;
|
||||
Flow flow = (layer_id == 0 && m_raft_layers == 0) ? m_object->print()->brim_flow() :
|
||||
(m_support_params.base_fill_pattern == ipRectilinear && (layer_id % num_layers_to_change_infill_direction == 0) ? support_transition_flow(m_object) : support_flow);
|
||||
if (with_infill && layer_id > 0) {
|
||||
if (with_infill && layer_id > 0 && m_support_params.base_fill_pattern != ipLightning) {
|
||||
if (m_support_params.base_fill_pattern == ipRectilinear) {
|
||||
role = erSupportMaterial;// layer_id% num_layers_to_change_infill_direction == 0 ? erSupportTransition : erSupportMaterial;
|
||||
filler_support->angle = Geometry::deg2rad(object_config.support_angle.value);// obj_is_vertical* M_PI_2;// (obj_is_vertical + int(layer_id / num_layers_to_change_infill_direction))* M_PI_2;
|
||||
|
@ -1541,6 +1589,67 @@ void TreeSupport::generate_toolpaths()
|
|||
}
|
||||
}
|
||||
}
|
||||
if (with_infill && m_support_params.base_fill_pattern == ipLightning)
|
||||
{
|
||||
double print_z = ts_layer->print_z;
|
||||
if (printZ_to_lightninglayer.find(print_z) == printZ_to_lightninglayer.end())
|
||||
continue;
|
||||
//TODO:
|
||||
//1.the second parameter of convertToLines seems to decide how long the lightning should be trimmed from its root, so that the root wont overlap/detach the support contour.
|
||||
// whether current value works correctly remained to be tested
|
||||
//2.related to previous one, that lightning roots need to be trimed more when support has multiple walls
|
||||
//3.function connect_infill() and variable 'params' helps create connection pattern along contours between two lightning roots,
|
||||
// strengthen lightnings while it may make support harder. decide to enable it or not. if yes, proper values for params are remained to be tested
|
||||
auto& lightning_layer = generator->getTreesForLayer(printZ_to_lightninglayer[print_z]);
|
||||
|
||||
Flow flow = (layer_id == 0 && m_raft_layers == 0) ?
|
||||
m_object->print()->brim_flow() :
|
||||
(m_support_params.base_fill_pattern == ipRectilinear && (layer_id % num_layers_to_change_infill_direction == 0) ?
|
||||
support_transition_flow(m_object) :
|
||||
support_flow);
|
||||
ExPolygons areas = offset_ex(ts_layer->base_areas, -flow.scaled_spacing());
|
||||
|
||||
for (auto& area : areas)
|
||||
{
|
||||
Polylines polylines = lightning_layer.convertToLines(to_polygons(area), 0);
|
||||
for (auto itr = polylines.begin(); itr != polylines.end();)
|
||||
{
|
||||
if (itr->length() < scale_(1.0))
|
||||
itr = polylines.erase(itr);
|
||||
else
|
||||
itr++;
|
||||
}
|
||||
Polylines opt_polylines;
|
||||
#if 1
|
||||
//this wont create connection patterns along contours
|
||||
append(opt_polylines, chain_polylines(std::move(polylines)));
|
||||
#else
|
||||
//this will create connection patterns along contours
|
||||
FillParams params;
|
||||
params.anchor_length = float(Fill::infill_anchor * 0.01 * flow.spacing());
|
||||
params.anchor_length_max = Fill::infill_anchor_max;
|
||||
params.anchor_length = std::min(params.anchor_length, params.anchor_length_max);
|
||||
Fill::connect_infill(std::move(polylines), area, opt_polylines, flow.spacing(), params);
|
||||
#endif
|
||||
extrusion_entities_append_paths(ts_layer->support_fills.entities, opt_polylines, erSupportMaterial,
|
||||
float(flow.mm3_per_mm()), float(flow.width()), float(flow.height()));
|
||||
|
||||
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
|
||||
std::string prefix = "./SVG/";
|
||||
std::string suffix = ".svg";
|
||||
std::string name = prefix + "trees_polyline" + "_" + std::to_string(ts_layer->print_z) /*+ "_" + std::to_string(rand_num)*/ + suffix;
|
||||
BoundingBox bbox = get_extents(ts_layer->base_areas);
|
||||
SVG svg(name, bbox);
|
||||
|
||||
svg.draw(ts_layer->base_areas, "blue");
|
||||
svg.draw(generator->Overhangs()[printZ_to_lightninglayer[print_z]], "red");
|
||||
for (auto& line : opt_polylines)
|
||||
{
|
||||
svg.draw(line, "yellow");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// sort extrusions to reduce travel, also make sure walls go before infills
|
||||
chain_and_reorder_extrusion_entities(ts_layer->support_fills.entities);
|
||||
|
@ -1761,30 +1870,30 @@ void TreeSupport::generate_support_areas()
|
|||
if (!tree_support_enable)
|
||||
return;
|
||||
|
||||
std::vector<std::vector<Node*>> contact_nodes(m_object->layers().size()); //Generate empty layers to store the points in.
|
||||
std::vector<std::vector<Node*>> contact_nodes(m_object->layers().size());
|
||||
|
||||
profiler.stage_start(STAGE_total);
|
||||
|
||||
// Generate overhang areas
|
||||
profiler.stage_start(STAGE_DETECT_OVERHANGS);
|
||||
m_object->print()->set_status(55, _L("Support: detect overhangs"));
|
||||
detect_object_overhangs();
|
||||
detect_object_overhangs(); // Entry of step#1;
|
||||
profiler.stage_finish(STAGE_DETECT_OVERHANGS);
|
||||
|
||||
// Generate contact points of tree support
|
||||
profiler.stage_start(STAGE_GENERATE_CONTACT_NODES);
|
||||
m_object->print()->set_status(56, _L("Support: generate contact points"));
|
||||
generate_contact_points(contact_nodes);
|
||||
generate_contact_points(contact_nodes); // Entry of step#2;
|
||||
profiler.stage_finish(STAGE_GENERATE_CONTACT_NODES);
|
||||
|
||||
//Drop nodes to lower layers.
|
||||
profiler.stage_start(STAGE_DROP_DOWN_NODES);
|
||||
m_object->print()->set_status(60, _L("Support: propagate branches"));
|
||||
drop_nodes(contact_nodes);
|
||||
drop_nodes(contact_nodes); // Entry of step#3;
|
||||
profiler.stage_finish(STAGE_DROP_DOWN_NODES);
|
||||
|
||||
// Adjust support layer heights
|
||||
adjust_layer_heights(contact_nodes);
|
||||
adjust_layer_heights(contact_nodes); // Entry of step#4;
|
||||
|
||||
//Generate support areas.
|
||||
profiler.stage_start(STAGE_DRAW_CIRCLES);
|
||||
|
@ -1866,7 +1975,7 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
|
|||
const int CIRCLE_RESOLUTION = SQUARE_SUPPORT ? 4 : 100; // The number of vertices in each circle.
|
||||
|
||||
|
||||
for (unsigned int i = 0; i < CIRCLE_RESOLUTION; i++)
|
||||
for (int i = 0; i < CIRCLE_RESOLUTION; i++)
|
||||
{
|
||||
double angle;
|
||||
if (SQUARE_SUPPORT)
|
||||
|
@ -1895,6 +2004,15 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
|
|||
const coordf_t line_width = config.support_line_width;
|
||||
const coordf_t line_width_scaled = scale_(line_width);
|
||||
|
||||
const bool with_lightning_infill = config.tree_support_with_infill.value && config.support_base_pattern.value == smpLightning;
|
||||
coordf_t support_extrusion_width = config.support_line_width.value > 0 ? config.support_line_width : config.line_width;
|
||||
const size_t wall_count = config.tree_support_wall_count.value;
|
||||
|
||||
const PrintObjectConfig& object_config = m_object->config();
|
||||
auto m_support_material_flow = support_material_flow(m_object, float(m_slicing_params.layer_height));
|
||||
coordf_t support_spacing = object_config.support_base_pattern_spacing.value + m_support_material_flow.spacing();
|
||||
coordf_t support_density = std::min(1., m_support_material_flow.spacing() / support_spacing);
|
||||
|
||||
// coconut: previously std::unordered_map in m_collision_cache is not multi-thread safe which may cause programs stuck, here we change to tbb::concurrent_unordered_map
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, m_object->layer_count()),
|
||||
|
@ -1924,6 +2042,7 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
|
|||
ExPolygons& roof_1st_layer = ts_layer->roof_1st_layer;
|
||||
ExPolygons& floor_areas = ts_layer->floor_areas;
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "circles at layer " << layer_nr << " contact nodes size=" << contact_nodes[layer_nr].size();
|
||||
//Draw the support areas and add the roofs appropriately to the support roof instead of normal areas.
|
||||
ts_layer->lslices.reserve(contact_nodes[layer_nr].size());
|
||||
for (const Node* p_node : contact_nodes[layer_nr])
|
||||
|
@ -2060,100 +2179,168 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
|
|||
});
|
||||
|
||||
#if 1
|
||||
// move the holes to contour so they can be well supported
|
||||
if (!has_infill) {
|
||||
// check if poly's contour intersects with expoly's contour
|
||||
auto intersects_contour = [](Polygon poly, ExPolygon expoly, Point &pt_on_poly, Point &pt_on_expoly, Point &pt_far_on_poly, float dist_thresh = 0.01) {
|
||||
float min_dist = std::numeric_limits<float>::max();
|
||||
float max_dist = 0;
|
||||
for (auto from : poly.points) {
|
||||
for (int i = 0; i < expoly.num_contours(); i++) {
|
||||
const Point *candidate = expoly.contour_or_hole(i).closest_point(from);
|
||||
double dist2 = vsize2_with_unscale(*candidate - from);
|
||||
if (dist2 < min_dist) {
|
||||
min_dist = dist2;
|
||||
pt_on_poly = from;
|
||||
pt_on_expoly = *candidate;
|
||||
}
|
||||
if (dist2 > max_dist) {
|
||||
max_dist = dist2;
|
||||
pt_far_on_poly = from;
|
||||
}
|
||||
if (dist2 < dist_thresh) { return true; }
|
||||
if (with_lightning_infill)
|
||||
{
|
||||
const bool global_lightning_infill = true;
|
||||
|
||||
std::vector<Polygons> contours;
|
||||
std::vector<Polygons> overhangs;
|
||||
for (int layer_nr = 1; layer_nr < m_object->layer_count(); layer_nr++) {
|
||||
if (print->canceled()) break;
|
||||
const std::vector<Node*>& curr_layer_nodes = contact_nodes[layer_nr];
|
||||
TreeSupportLayer* ts_layer = m_object->get_tree_support_layer(layer_nr + m_raft_layers);
|
||||
assert(ts_layer != nullptr);
|
||||
|
||||
// skip if current layer has no points. This fixes potential crash in get_collision (see jira BBL001-355)
|
||||
if (curr_layer_nodes.empty()) continue;
|
||||
if (ts_layer->height < EPSILON) continue;
|
||||
if (ts_layer->area_groups.empty()) continue;
|
||||
|
||||
ExPolygons& base_areas = ts_layer->base_areas;
|
||||
|
||||
int layer_nr_lower = layer_nr - 1;
|
||||
for (layer_nr_lower; layer_nr_lower >= 0; layer_nr_lower--) {
|
||||
if (!m_object->get_tree_support_layer(layer_nr_lower + m_raft_layers)->area_groups.empty()) break;
|
||||
}
|
||||
TreeSupportLayer* lower_layer = m_object->get_tree_support_layer(layer_nr_lower + m_raft_layers);
|
||||
ExPolygons& base_areas_lower = m_object->get_tree_support_layer(layer_nr_lower + m_raft_layers)->base_areas;
|
||||
|
||||
ExPolygons overhang;
|
||||
|
||||
if (layer_nr_lower == 0)
|
||||
continue;
|
||||
|
||||
if (global_lightning_infill)
|
||||
{
|
||||
//search overhangs globally
|
||||
overhang = std::move(diff_ex(offset_ex(base_areas_lower, -2.0 * scale_(support_extrusion_width)), base_areas));
|
||||
}
|
||||
else
|
||||
{
|
||||
//search overhangs only on floating islands
|
||||
for (auto& base_area : base_areas)
|
||||
for (auto& hole : base_area.holes)
|
||||
{
|
||||
Polygon rev_hole = hole;
|
||||
rev_hole.make_counter_clockwise();
|
||||
ExPolygons ex_hole = to_expolygons(ExPolygon(rev_hole));
|
||||
for (auto& other_area : base_areas)
|
||||
//if (&other_area != &base_area)
|
||||
ex_hole = std::move(diff_ex(ex_hole, other_area));
|
||||
overhang = std::move(union_ex(overhang, ex_hole));
|
||||
}
|
||||
overhang = std::move(intersection_ex(overhang, offset_ex(base_areas_lower, -0.5 * scale_(support_extrusion_width))));
|
||||
}
|
||||
|
||||
overhangs.emplace_back(to_polygons(overhang));
|
||||
contours.emplace_back(to_polygons(base_areas_lower)); //cant guarantee for 100% success probability, infill fails sometimes
|
||||
printZ_to_lightninglayer[lower_layer->print_z] = overhangs.size() - 1;
|
||||
|
||||
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
|
||||
draw_two_overhangs_to_svg(m_object->get_tree_support_layer(layer_nr_lower + m_raft_layers), base_areas_lower, to_expolygons(overhangs.back()));
|
||||
#endif
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// polygon pointer: depth, direction, farPoint
|
||||
std::map<const Polygon *, std::tuple<int, Point, Point>> holePropagationInfos;
|
||||
for (int layer_nr = m_object->layer_count() - 1; layer_nr > 0; layer_nr--) {
|
||||
if (print->canceled()) break;
|
||||
m_object->print()->set_status(66, (boost::format(_L("Support: fix holes at layer %d")) % layer_nr).str());
|
||||
generator = std::make_unique<FillLightning::Generator>(m_object, contours, overhangs, support_density);
|
||||
}
|
||||
|
||||
const std::vector<Node *> &curr_layer_nodes = contact_nodes[layer_nr];
|
||||
TreeSupportLayer * ts_layer = m_object->get_tree_support_layer(layer_nr + m_raft_layers);
|
||||
assert(ts_layer != nullptr);
|
||||
else if (!has_infill) {
|
||||
// move the holes to contour so they can be well supported
|
||||
|
||||
// skip if current layer has no points. This fixes potential crash in get_collision (see jira BBL001-355)
|
||||
if (curr_layer_nodes.empty()) continue;
|
||||
if (ts_layer->height < EPSILON) continue;
|
||||
if (ts_layer->area_groups.empty()) continue;
|
||||
|
||||
int layer_nr_lower = layer_nr - 1;
|
||||
for (layer_nr_lower; layer_nr_lower >= 0; layer_nr_lower--) {
|
||||
if (!m_object->get_tree_support_layer(layer_nr_lower + m_raft_layers)->area_groups.empty()) break;
|
||||
}
|
||||
auto &area_groups_lower = m_object->get_tree_support_layer(layer_nr_lower + m_raft_layers)->area_groups;
|
||||
|
||||
for (const auto &area_group : ts_layer->area_groups) {
|
||||
if (area_group.second != TreeSupportLayer::BaseType) continue;
|
||||
const auto area = area_group.first;
|
||||
for (const auto &hole : area->holes) {
|
||||
// auto hole_bbox = get_extents(hole).polygon();
|
||||
for (auto &area_group_lower : area_groups_lower) {
|
||||
if (area_group.second != TreeSupportLayer::BaseType) continue;
|
||||
auto &base_area_lower = *area_group_lower.first;
|
||||
Point pt_on_poly, pt_on_expoly, pt_far_on_poly;
|
||||
// if a hole doesn't intersect with lower layer's contours, add a hole to lower layer and move it slightly to the contour
|
||||
if (base_area_lower.contour.contains(hole.points.front()) && !intersects_contour(hole, base_area_lower, pt_on_poly, pt_on_expoly, pt_far_on_poly)) {
|
||||
Polygon hole_lower = hole;
|
||||
Point direction = normal(pt_on_expoly - pt_on_poly, line_width_scaled / 2);
|
||||
hole_lower.translate(direction);
|
||||
// note to expand a hole, we need to do negative offset
|
||||
auto hole_expanded = offset(hole_lower, -line_width_scaled / 4, ClipperLib::JoinType::jtSquare);
|
||||
if (!hole_expanded.empty()) {
|
||||
base_area_lower.holes.push_back(std::move(hole_expanded[0]));
|
||||
holePropagationInfos.insert({&base_area_lower.holes.back(), {25, direction, pt_far_on_poly}});
|
||||
// check if poly's contour intersects with expoly's contour
|
||||
auto intersects_contour = [](Polygon poly, ExPolygon expoly, Point& pt_on_poly, Point& pt_on_expoly, Point& pt_far_on_poly, float dist_thresh = 0.01) {
|
||||
float min_dist = std::numeric_limits<float>::max();
|
||||
float max_dist = 0;
|
||||
for (auto from : poly.points) {
|
||||
for (int i = 0; i < expoly.num_contours(); i++) {
|
||||
const Point* candidate = expoly.contour_or_hole(i).closest_point(from);
|
||||
double dist2 = vsize2_with_unscale(*candidate - from);
|
||||
if (dist2 < min_dist) {
|
||||
min_dist = dist2;
|
||||
pt_on_poly = from;
|
||||
pt_on_expoly = *candidate;
|
||||
}
|
||||
break;
|
||||
} else if (holePropagationInfos.find(&hole) != holePropagationInfos.end() && std::get<0>(holePropagationInfos[&hole]) > 0 &&
|
||||
base_area_lower.contour.contains(std::get<2>(holePropagationInfos[&hole]))) {
|
||||
Polygon hole_lower = hole;
|
||||
auto && direction = std::get<1>(holePropagationInfos[&hole]);
|
||||
hole_lower.translate(direction);
|
||||
// note to shrink a hole, we need to do positive offset
|
||||
auto hole_expanded = offset(hole_lower, line_width_scaled / 2, ClipperLib::JoinType::jtSquare);
|
||||
Point farPoint = std::get<2>(holePropagationInfos[&hole]) + direction * 2;
|
||||
if (!hole_expanded.empty()) {
|
||||
base_area_lower.holes.push_back(std::move(hole_expanded[0]));
|
||||
holePropagationInfos.insert({&base_area_lower.holes.back(), {std::get<0>(holePropagationInfos[&hole]) - 1, direction, farPoint}});
|
||||
if (dist2 > max_dist) {
|
||||
max_dist = dist2;
|
||||
pt_far_on_poly = from;
|
||||
}
|
||||
break;
|
||||
if (dist2 < dist_thresh) { return true; }
|
||||
}
|
||||
}
|
||||
{
|
||||
return false;
|
||||
};
|
||||
|
||||
// polygon pointer: depth, direction, farPoint
|
||||
std::map<const Polygon*, std::tuple<int, Point, Point>> holePropagationInfos;
|
||||
for (int layer_nr = m_object->layer_count() - 1; layer_nr > 0; layer_nr--) {
|
||||
if (print->canceled()) break;
|
||||
m_object->print()->set_status(66, (boost::format(_L("Support: fix holes at layer %d")) % layer_nr).str());
|
||||
|
||||
const std::vector<Node*>& curr_layer_nodes = contact_nodes[layer_nr];
|
||||
TreeSupportLayer* ts_layer = m_object->get_tree_support_layer(layer_nr + m_raft_layers);
|
||||
assert(ts_layer != nullptr);
|
||||
|
||||
// skip if current layer has no points. This fixes potential crash in get_collision (see jira BBL001-355)
|
||||
if (curr_layer_nodes.empty()) continue;
|
||||
if (ts_layer->height < EPSILON) continue;
|
||||
if (ts_layer->area_groups.empty()) continue;
|
||||
|
||||
int layer_nr_lower = layer_nr - 1;
|
||||
for (layer_nr_lower; layer_nr_lower >= 0; layer_nr_lower--) {
|
||||
if (!m_object->get_tree_support_layer(layer_nr_lower + m_raft_layers)->area_groups.empty()) break;
|
||||
}
|
||||
if (layer_nr_lower < 0) continue;
|
||||
auto& area_groups_lower = m_object->get_tree_support_layer(layer_nr_lower + m_raft_layers)->area_groups;
|
||||
|
||||
for (const auto& area_group : ts_layer->area_groups) {
|
||||
if (area_group.second != TreeSupportLayer::BaseType) continue;
|
||||
const auto area = area_group.first;
|
||||
for (const auto& hole : area->holes) {
|
||||
// auto hole_bbox = get_extents(hole).polygon();
|
||||
for (auto& area_group_lower : area_groups_lower) {
|
||||
if (area_group.second != TreeSupportLayer::BaseType) continue;
|
||||
auto& base_area_lower = *area_group_lower.first;
|
||||
Point pt_on_poly, pt_on_expoly, pt_far_on_poly;
|
||||
// if a hole doesn't intersect with lower layer's contours, add a hole to lower layer and move it slightly to the contour
|
||||
if (base_area_lower.contour.contains(hole.points.front()) && !intersects_contour(hole, base_area_lower, pt_on_poly, pt_on_expoly, pt_far_on_poly)) {
|
||||
Polygon hole_lower = hole;
|
||||
Point direction = normal(pt_on_expoly - pt_on_poly, line_width_scaled / 2);
|
||||
hole_lower.translate(direction);
|
||||
// note to expand a hole, we need to do negative offset
|
||||
auto hole_expanded = offset(hole_lower, -line_width_scaled / 4, ClipperLib::JoinType::jtSquare);
|
||||
if (!hole_expanded.empty()) {
|
||||
base_area_lower.holes.push_back(std::move(hole_expanded[0]));
|
||||
holePropagationInfos.insert({ &base_area_lower.holes.back(), {25, direction, pt_far_on_poly} });
|
||||
}
|
||||
break;
|
||||
}
|
||||
else if (holePropagationInfos.find(&hole) != holePropagationInfos.end() && std::get<0>(holePropagationInfos[&hole]) > 0 &&
|
||||
base_area_lower.contour.contains(std::get<2>(holePropagationInfos[&hole]))) {
|
||||
Polygon hole_lower = hole;
|
||||
auto&& direction = std::get<1>(holePropagationInfos[&hole]);
|
||||
hole_lower.translate(direction);
|
||||
// note to shrink a hole, we need to do positive offset
|
||||
auto hole_expanded = offset(hole_lower, line_width_scaled / 2, ClipperLib::JoinType::jtSquare);
|
||||
Point farPoint = std::get<2>(holePropagationInfos[&hole]) + direction * 2;
|
||||
if (!hole_expanded.empty()) {
|
||||
base_area_lower.holes.push_back(std::move(hole_expanded[0]));
|
||||
holePropagationInfos.insert({ &base_area_lower.holes.back(), {std::get<0>(holePropagationInfos[&hole]) - 1, direction, farPoint} });
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
{
|
||||
// if roof1 interface is inside a hole, need to expand the interface
|
||||
for (auto &roof1 : ts_layer->roof_1st_layer) {
|
||||
for (auto& roof1 : ts_layer->roof_1st_layer) {
|
||||
//if (hole.contains(roof1.contour.points.front()) && hole.contains(roof1.contour.bounding_box().center()))
|
||||
bool is_inside_hole = std::all_of(roof1.contour.points.begin(), roof1.contour.points.end(), [&hole](Point &pt) { return hole.contains(pt); });
|
||||
bool is_inside_hole = std::all_of(roof1.contour.points.begin(), roof1.contour.points.end(), [&hole](Point& pt) { return hole.contains(pt); });
|
||||
if (is_inside_hole) {
|
||||
Polygon hole_reoriented = hole;
|
||||
if (roof1.contour.is_counter_clockwise())
|
||||
hole_reoriented.make_counter_clockwise();
|
||||
else if (roof1.contour.is_clockwise())
|
||||
hole_reoriented.make_clockwise();
|
||||
auto tmp = union_({roof1.contour}, {hole_reoriented});
|
||||
auto tmp = union_({ roof1.contour }, { hole_reoriented });
|
||||
if (!tmp.empty()) roof1.contour = tmp[0];
|
||||
|
||||
// make sure 1) roof1 and object 2) roof1 and roof, won't intersect
|
||||
|
@ -2163,38 +2350,38 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
|
|||
tmp1 = diff_ex(tmp1, ts_layer->roof_areas);
|
||||
if (!tmp1.empty()) {
|
||||
roof1.contour = std::move(tmp1[0].contour);
|
||||
roof1.holes = std::move(tmp1[0].holes);
|
||||
}
|
||||
roof1.holes = std::move(tmp1[0].holes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
|
||||
for (int layer_nr = m_object->layer_count() - 1; layer_nr > 0; layer_nr--) {
|
||||
TreeSupportLayer* ts_layer = m_object->get_tree_support_layer(layer_nr + m_raft_layers);
|
||||
ExPolygons& base_areas = ts_layer->base_areas;
|
||||
ExPolygons& roof_areas = ts_layer->roof_areas;
|
||||
ExPolygons& roof_1st_layer = ts_layer->roof_1st_layer;
|
||||
ExPolygons& floor_areas = ts_layer->floor_areas;
|
||||
if (base_areas.empty() && roof_areas.empty() && roof_1st_layer.empty()) continue;
|
||||
char fname[10]; sprintf(fname, "%d_%.2f", layer_nr, ts_layer->print_z);
|
||||
draw_contours_and_nodes_to_svg(-1, base_areas, roof_areas, roof_1st_layer, {}, {}, get_svg_filename(fname, "circles"), {"base", "roof", "roof1st"});
|
||||
}
|
||||
for (int layer_nr = m_object->layer_count() - 1; layer_nr > 0; layer_nr--) {
|
||||
TreeSupportLayer* ts_layer = m_object->get_tree_support_layer(layer_nr + m_raft_layers);
|
||||
ExPolygons& base_areas = ts_layer->base_areas;
|
||||
ExPolygons& roof_areas = ts_layer->roof_areas;
|
||||
ExPolygons& roof_1st_layer = ts_layer->roof_1st_layer;
|
||||
ExPolygons& floor_areas = ts_layer->floor_areas;
|
||||
if (base_areas.empty() && roof_areas.empty() && roof_1st_layer.empty()) continue;
|
||||
char fname[10]; sprintf(fname, "%d_%.2f", layer_nr, ts_layer->print_z);
|
||||
draw_contours_and_nodes_to_svg(-1, base_areas, roof_areas, roof_1st_layer, {}, {}, get_svg_filename(fname, "circles"), { "base", "roof", "roof1st" });
|
||||
}
|
||||
#endif
|
||||
|
||||
TreeSupportLayerPtrs& ts_layers = m_object->tree_support_layers();
|
||||
auto iter = std::remove_if(ts_layers.begin(), ts_layers.end(), [](TreeSupportLayer* ts_layer) { return ts_layer->height < EPSILON; });
|
||||
ts_layers.erase(iter, ts_layers.end());
|
||||
for (int layer_nr = 0; layer_nr < ts_layers.size(); layer_nr++) {
|
||||
ts_layers[layer_nr]->upper_layer = layer_nr != ts_layers.size() - 1 ? ts_layers[layer_nr + 1] : nullptr;
|
||||
ts_layers[layer_nr]->lower_layer = layer_nr > 0 ? ts_layers[layer_nr - 1] : nullptr;
|
||||
}
|
||||
TreeSupportLayerPtrs& ts_layers = m_object->tree_support_layers();
|
||||
auto iter = std::remove_if(ts_layers.begin(), ts_layers.end(), [](TreeSupportLayer* ts_layer) { return ts_layer->height < EPSILON; });
|
||||
ts_layers.erase(iter, ts_layers.end());
|
||||
for (int layer_nr = 0; layer_nr < ts_layers.size(); layer_nr++) {
|
||||
ts_layers[layer_nr]->upper_layer = layer_nr != ts_layers.size() - 1 ? ts_layers[layer_nr + 1] : nullptr;
|
||||
ts_layers[layer_nr]->lower_layer = layer_nr > 0 ? ts_layers[layer_nr - 1] : nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void TreeSupport::drop_nodes(std::vector<std::vector<Node*>>& contact_nodes)
|
||||
|
@ -2268,16 +2455,6 @@ void TreeSupport::drop_nodes(std::vector<std::vector<Node*>>& contact_nodes)
|
|||
continue;
|
||||
m_object->print()->set_status(60, (boost::format(_L("Support: propagate branches at layer %d")) % layer_nr).str());
|
||||
|
||||
for (Node* p_node : layer_contact_nodes)
|
||||
{
|
||||
if (p_node->type == ePolygon) {
|
||||
const bool to_buildplate = !is_inside_ex(m_ts_data->m_layer_outlines[layer_nr], p_node->position);
|
||||
Node * next_node = new Node(p_node->position, p_node->distance_to_top + 1, p_node->skin_direction, p_node->support_roof_layers_below - 1, to_buildplate, p_node,
|
||||
m_object->get_layer(layer_nr - 1)->print_z, m_object->get_layer(layer_nr - 1)->height);
|
||||
contact_nodes[layer_nr - 1].emplace_back(next_node);
|
||||
}
|
||||
}
|
||||
|
||||
Polygons layer_contours = std::move(m_ts_data->get_contours_with_holes(layer_nr));
|
||||
//std::unordered_map<Line, bool, LineHash>& mst_line_x_layer_contour_cache = m_mst_line_x_layer_contour_caches[layer_nr];
|
||||
std::unordered_map<Line, bool, LineHash> mst_line_x_layer_contour_cache;
|
||||
|
@ -2320,6 +2497,10 @@ void TreeSupport::drop_nodes(std::vector<std::vector<Node*>>& contact_nodes)
|
|||
}
|
||||
if (node.type == ePolygon) {
|
||||
// polygon node do not merge or move
|
||||
const bool to_buildplate = !is_inside_ex(m_ts_data->m_layer_outlines[layer_nr], p_node->position);
|
||||
Node *next_node = new Node(p_node->position, p_node->distance_to_top + 1, p_node->skin_direction, p_node->support_roof_layers_below - 1, to_buildplate, p_node,
|
||||
m_object->get_layer(layer_nr - 1)->print_z, m_object->get_layer(layer_nr - 1)->height);
|
||||
contact_nodes[layer_nr - 1].emplace_back(next_node);
|
||||
continue;
|
||||
}
|
||||
/* Find which part this node is located in and group the nodes in
|
||||
|
@ -2409,7 +2590,8 @@ void TreeSupport::drop_nodes(std::vector<std::vector<Node*>>& contact_nodes)
|
|||
size_t new_support_roof_layers_below = std::max(node.support_roof_layers_below, neighbour->support_roof_layers_below) - 1;
|
||||
|
||||
const bool to_buildplate = !is_inside_ex(m_ts_data->get_avoidance(0, layer_nr - 1), next_position);
|
||||
Node* next_node = new Node(next_position, new_distance_to_top, node.skin_direction, new_support_roof_layers_below, to_buildplate, p_node,p_node->print_z,p_node->height);
|
||||
Node * next_node = new Node(next_position, new_distance_to_top, node.skin_direction, new_support_roof_layers_below, to_buildplate, p_node,
|
||||
m_object->get_layer(layer_nr - 1)->print_z, p_node->height);
|
||||
next_node->movement = next_position - node.position;
|
||||
contact_nodes[layer_nr - 1].push_back(next_node);
|
||||
|
||||
|
@ -2457,21 +2639,20 @@ void TreeSupport::drop_nodes(std::vector<std::vector<Node*>>& contact_nodes)
|
|||
{
|
||||
unsupported_branch_leaves.push_front({ layer_nr, p_node });
|
||||
}
|
||||
/*else {
|
||||
else {
|
||||
Node* pn = p_node;
|
||||
for (int i = 0; i < bottom_interface_layers && pn; i++, pn = pn->parent)
|
||||
pn->support_floor_layers_above = bottom_interface_layers - i;
|
||||
for (int i = 0; i <= bottom_interface_layers && pn; i++, pn = pn->parent)
|
||||
pn->support_floor_layers_above = bottom_interface_layers - i + 1; // +1 so the parent node has support_floor_layers_above=2
|
||||
to_delete.insert(p_node);
|
||||
}*/
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// if the link between parent and current is cut by contours, delete this branch
|
||||
// if the link between parent and current is cut by contours, mark current as bottom contact node
|
||||
if (p_node->parent && intersection_ln({p_node->position, p_node->parent->position}, layer_contours).empty()==false)
|
||||
{
|
||||
//unsupported_branch_leaves.push_front({ layer_nr, p_node });
|
||||
Node* pn = p_node->parent;
|
||||
for (int i = 0; i < bottom_interface_layers && pn; i++, pn = pn->parent)
|
||||
pn->support_floor_layers_above = bottom_interface_layers - i;
|
||||
for (int i = 0; i <= bottom_interface_layers && pn; i++, pn = pn->parent)
|
||||
pn->support_floor_layers_above = bottom_interface_layers - i + 1;
|
||||
to_delete.insert(p_node);
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "tbb/concurrent_unordered_map.h"
|
||||
#include "Flow.hpp"
|
||||
#include "PrintConfig.hpp"
|
||||
#include "Fill/Lightning/Generator.hpp"
|
||||
|
||||
#ifndef SQ
|
||||
#define SQ(x) ((x)*(x))
|
||||
|
@ -350,6 +351,9 @@ public:
|
|||
int avg_node_per_layer = 0;
|
||||
float nodes_angle = 0;
|
||||
bool has_sharp_tail;
|
||||
|
||||
std::unique_ptr<FillLightning::Generator> generator;
|
||||
std::unordered_map<double, size_t> printZ_to_lightninglayer;
|
||||
private:
|
||||
/*!
|
||||
* \brief Generator for model collision, avoidance and internal guide volumes
|
||||
|
|
|
@ -399,7 +399,7 @@ struct SlabLines {
|
|||
std::vector<IntersectionLines> at_slice;
|
||||
// Projections of triangle set boundary lines into layer below (for projection from the top)
|
||||
// or into layer above (for projection from the bottom).
|
||||
// In both cases the intersection liens are CCW oriented.
|
||||
// In both cases the intersection lines are CCW oriented.
|
||||
std::vector<IntersectionLines> between_slices;
|
||||
};
|
||||
|
||||
|
@ -790,7 +790,8 @@ inline std::pair<SlabLines, SlabLines> slice_slabs_make_lines(
|
|||
}
|
||||
slice_facet_with_slabs<true>(vertices, indices, face_idx, neighbors, edge_ids, num_edges, zs, lines_top, lines_mutex_top);
|
||||
}
|
||||
if (bottom && (fo == FaceOrientation::Down || fo == FaceOrientation::Degenerate)) {
|
||||
// BBS: add vertical faces option
|
||||
if (bottom && (fo == FaceOrientation::Down || fo == FaceOrientation::Vertical || fo == FaceOrientation::Degenerate)) {
|
||||
Vec3i neighbors = face_neighbors[face_idx];
|
||||
// Reset neighborship of this triangle in case the other triangle is oriented backwards from this one.
|
||||
for (int i = 0; i < 3; ++ i)
|
||||
|
|
|
@ -70,12 +70,8 @@ static constexpr double SUPPORT_RESOLUTION = 0.05;
|
|||
static constexpr double INSET_OVERLAP_TOLERANCE = 0.4;
|
||||
// 3mm ring around the top / bottom / bridging areas.
|
||||
//FIXME This is quite a lot.
|
||||
// BBS: 3mm is too large and will cause overflow when printing object which likes shell.
|
||||
// We decided to reduce this value according to superslicer.
|
||||
// The right way is that area should not be enlarged. But should find arched point at last layer, expecially for
|
||||
// bridge area.
|
||||
//static constexpr double EXTERNAL_INFILL_MARGIN = 3;
|
||||
static constexpr double EXTERNAL_INFILL_MARGIN = 1;
|
||||
static constexpr double EXTERNAL_INFILL_MARGIN = 3;
|
||||
static constexpr double BRIDGE_INFILL_MARGIN = 1;
|
||||
//FIXME Better to use an inline function with an explicit return type.
|
||||
//inline coord_t scale_(coordf_t v) { return coord_t(floor(v / SCALING_FACTOR + 0.5f)); }
|
||||
#define scale_(val) ((val) / SCALING_FACTOR)
|
||||
|
|
|
@ -7,5 +7,6 @@
|
|||
#define SLIC3R_BUILD_ID "@SLIC3R_BUILD_ID@"
|
||||
//#define SLIC3R_RC_VERSION "@SLIC3R_VERSION@"
|
||||
#define BBL_RELEASE_TO_PUBLIC @BBL_RELEASE_TO_PUBLIC@
|
||||
#define BBL_INTERNAL_TESTING @BBL_INTERNAL_TESTING@
|
||||
|
||||
#endif /* __SLIC3R_VERSION_H */
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue