Merge branch 'master' of https://github.com/Prusa-Development/PrusaSlicerPrivate into et_transformations

This commit is contained in:
enricoturri1966 2023-03-07 11:28:49 +01:00
commit c28645b174
148 changed files with 83207 additions and 65997 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,5 @@
min_slic3r_version = 2.4.1-beta3
0.0.7 Correct missing profile improvement from 0.0.6 to set ensure_vertical_shell_thickness = 0 (off).
0.0.6 Correct start gcode, match profile and firmware settings, and other profile improvements.
0.0.5 Correct Marlin Error accumulation for Ditto printer profiles.
0.0.4 Correct Marlin Error accumulation

View File

@ -5,7 +5,7 @@
name = BIBO
# Configuration version of this file. Config file will only be installed, if the config_version differs.
# This means, the server may force the PrusaSlicer configuration to be downgraded.
config_version = 0.0.6
config_version = 0.0.7
# Where to get the updates from?
config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/BIBO/
@ -38,7 +38,7 @@ compatible_printers =
complete_objects = 0
dont_support_bridges = 1
elefant_foot_compensation = 0.3
ensure_vertical_shell_thickness = 1
ensure_vertical_shell_thickness = 0
external_fill_pattern = rectilinear
external_perimeters_first = 0
external_perimeter_extrusion_width = 0.40

View File

@ -1,4 +1,5 @@
min_slic3r_version = 2.6.0-alpha1
1.7.0-alpha0 Added profiles for Print With Smile filaments.
1.6.0-alpha2 Added profile for Prusament PETG Carbon Fiber and Fiberthree F3 PA-GF30 Pro. Updated acceleration settings for Prusa MINI.
1.6.0-alpha1 Updated FW version notification. Decreased min layer time for PLA.
1.6.0-alpha0 Default top fill set to monotonic lines. Updated infill/perimeter overlap values. Updated output filename format. Enabled dynamic overhang speeds.

View File

@ -5,7 +5,7 @@
name = Prusa Research
# Configuration version of this file. Config file will only be installed, if the config_version differs.
# This means, the server may force the PrusaSlicer configuration to be downgraded.
config_version = 1.6.0-alpha2
config_version = 1.7.0-alpha0
# Where to get the updates from?
config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaResearch/
changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1%
@ -4227,6 +4227,9 @@ max_fan_speed = 40
bridge_fan_speed = 70
filament_max_volumetric_speed = 11
[filament:Print With Smile ASA @MINI]
inherits = Print With Smile ASA; *ABSMINI*
[filament:Print With Smile ABS]
inherits = *ABSC*
filament_vendor = Print With Smile
@ -4235,6 +4238,9 @@ filament_density = 1.08
first_layer_temperature = 240
temperature = 240
[filament:Print With Smile ABS @MINI]
inherits = Print With Smile ABS; *ABSMINI*
[filament:Print With Smile PETG CF]
inherits = Extrudr XPETG CF
filament_vendor = Print With Smile

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@ -4109,19 +4109,40 @@ void AddPolyNodeToPaths(const PolyNode& polynode, NodeType nodetype, Paths& path
for (int i = 0; i < polynode.ChildCount(); ++i)
AddPolyNodeToPaths(*polynode.Childs[i], nodetype, paths);
}
void AddPolyNodeToPaths(PolyNode&& polynode, NodeType nodetype, Paths& paths)
{
bool match = true;
if (nodetype == ntClosed) match = !polynode.IsOpen();
else if (nodetype == ntOpen) return;
if (!polynode.Contour.empty() && match)
paths.push_back(std::move(polynode.Contour));
for (int i = 0; i < polynode.ChildCount(); ++i)
AddPolyNodeToPaths(std::move(*polynode.Childs[i]), nodetype, paths);
}
//------------------------------------------------------------------------------
void PolyTreeToPaths(const PolyTree& polytree, Paths& paths)
{
paths.resize(0);
paths.clear();
paths.reserve(polytree.Total());
AddPolyNodeToPaths(polytree, ntAny, paths);
}
void PolyTreeToPaths(PolyTree&& polytree, Paths& paths)
{
paths.clear();
paths.reserve(polytree.Total());
AddPolyNodeToPaths(std::move(polytree), ntAny, paths);
}
//------------------------------------------------------------------------------
void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths)
{
paths.resize(0);
paths.clear();
paths.reserve(polytree.Total());
AddPolyNodeToPaths(polytree, ntClosed, paths);
}
@ -4129,7 +4150,7 @@ void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths)
void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths)
{
paths.resize(0);
paths.clear();
paths.reserve(polytree.Total());
//Open paths are top level only, so ...
for (int i = 0; i < polytree.ChildCount(); ++i)

View File

@ -206,6 +206,7 @@ void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool
void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution);
void PolyTreeToPaths(const PolyTree& polytree, Paths& paths);
void PolyTreeToPaths(PolyTree&& polytree, Paths& paths);
void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths);
void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths);

View File

@ -13,6 +13,7 @@
#include <Eigen/Geometry>
#include "BoundingBox.hpp"
#include "Utils.hpp" // for next_highest_power_of_2()
// Definition of the ray intersection hit structure.
@ -217,6 +218,23 @@ using Tree3f = Tree<3, float>;
using Tree2d = Tree<2, double>;
using Tree3d = Tree<3, double>;
// Wrap a 2D Slic3r own BoundingBox to be passed to Tree::build() and similar
// to build an AABBTree over coord_t 2D bounding boxes.
class BoundingBoxWrapper {
public:
using BoundingBox = Eigen::AlignedBox<coord_t, 2>;
BoundingBoxWrapper(const size_t idx, const Slic3r::BoundingBox &bbox) :
m_idx(idx),
// Inflate the bounding box a bit to account for numerical issues.
m_bbox(bbox.min - Point(SCALED_EPSILON, SCALED_EPSILON), bbox.max + Point(SCALED_EPSILON, SCALED_EPSILON)) {}
size_t idx() const { return m_idx; }
const BoundingBox& bbox() const { return m_bbox; }
Point centroid() const { return ((m_bbox.min().cast<int64_t>() + m_bbox.max().cast<int64_t>()) / 2).cast<int32_t>(); }
private:
size_t m_idx;
BoundingBox m_bbox;
};
namespace detail {
template<typename AVertexType, typename AIndexedFaceType, typename ATreeType, typename AVectorType>
struct RayIntersector {

View File

@ -0,0 +1,539 @@
#include "RegionExpansion.hpp"
#include <libslic3r/AABBTreeIndirect.hpp>
#include <libslic3r/ClipperZUtils.hpp>
#include <libslic3r/ClipperUtils.hpp>
#include <libslic3r/Utils.hpp>
#include <numeric>
namespace Slic3r {
namespace Algorithm {
// Calculating radius discretization according to ClipperLib offsetter code, see void ClipperOffset::DoOffset(double delta)
inline double clipper_round_offset_error(double offset, double arc_tolerance)
{
static constexpr const double def_arc_tolerance = 0.25;
const double y =
arc_tolerance <= 0 ?
def_arc_tolerance :
arc_tolerance > offset * def_arc_tolerance ?
offset * def_arc_tolerance :
arc_tolerance;
double steps = std::min(M_PI / std::acos(1. - y / offset), offset * M_PI);
return offset * (1. - cos(M_PI / steps));
}
RegionExpansionParameters RegionExpansionParameters::build(
// Scaled expansion value
float full_expansion,
// Expand by waves of expansion_step size (expansion_step is scaled).
float expansion_step,
// Don't take more than max_nr_steps for small expansion_step.
size_t max_nr_expansion_steps)
{
assert(full_expansion > 0);
assert(expansion_step > 0);
assert(max_nr_expansion_steps > 0);
RegionExpansionParameters out;
// Initial expansion of src to make the source regions intersect with boundary regions just a bit.
// The expansion should not be too tiny, but also small enough, so the following expansion will
// compensate for tiny_expansion and bring the wave back to the boundary without producing
// ugly cusps where it touches the boundary.
out.tiny_expansion = std::min(0.25f * full_expansion, scaled<float>(0.05f));
size_t nsteps = size_t(ceil((full_expansion - out.tiny_expansion) / expansion_step));
if (max_nr_expansion_steps > 0)
nsteps = std::min(nsteps, max_nr_expansion_steps);
assert(nsteps > 0);
out.initial_step = (full_expansion - out.tiny_expansion) / nsteps;
if (nsteps > 1 && 0.25 * out.initial_step < out.tiny_expansion) {
// Decrease the step size by lowering number of steps.
nsteps = std::max<size_t>(1, (floor((full_expansion - out.tiny_expansion) / (4. * out.tiny_expansion))));
out.initial_step = (full_expansion - out.tiny_expansion) / nsteps;
}
if (0.25 * out.initial_step < out.tiny_expansion || nsteps == 1) {
out.tiny_expansion = 0.2f * full_expansion;
out.initial_step = 0.8f * full_expansion;
}
out.other_step = out.initial_step;
out.num_other_steps = nsteps - 1;
// Accuracy of the offsetter for wave propagation.
out.arc_tolerance = scaled<double>(0.1);
out.shortest_edge_length = out.initial_step * ClipperOffsetShortestEdgeFactor;
// Maximum inflation of seed contours over the boundary. Used to trim boundary to speed up
// clipping during wave propagation. Needs to be in sync with the offsetter accuracy.
// Clipper positive round offset should rather offset less than more.
// Still a little bit of additional offset was added.
out.max_inflation = (out.tiny_expansion + nsteps * out.initial_step) * 1.1;
// (clipper_round_offset_error(out.tiny_expansion, co.ArcTolerance) + nsteps * clipper_round_offset_error(out.initial_step, co.ArcTolerance) * 1.5; // Account for uncertainty
return out;
}
// similar to expolygons_to_zpaths(), but each contour is expanded before converted to zpath.
// The expanded contours are then opened (the first point is repeated at the end).
static ClipperLib_Z::Paths expolygons_to_zpaths_expanded_opened(
const ExPolygons &src, const float expansion, coord_t &base_idx)
{
ClipperLib_Z::Paths out;
out.reserve(2 * std::accumulate(src.begin(), src.end(), size_t(0),
[](const size_t acc, const ExPolygon &expoly) { return acc + expoly.num_contours(); }));
ClipperLib::ClipperOffset offsetter;
offsetter.ShortestEdgeLength = expansion * ClipperOffsetShortestEdgeFactor;
ClipperLib::Paths expansion_cache;
for (const ExPolygon &expoly : src) {
for (size_t icontour = 0; icontour < expoly.num_contours(); ++ icontour) {
// Execute reorients the contours so that the outer most contour has a positive area. Thus the output
// contours will be CCW oriented even though the input paths are CW oriented.
// Offset is applied after contour reorientation, thus the signum of the offset value is reversed.
offsetter.Clear();
offsetter.AddPath(expoly.contour_or_hole(icontour).points, ClipperLib::jtSquare, ClipperLib::etClosedPolygon);
expansion_cache.clear();
offsetter.Execute(expansion_cache, icontour == 0 ? expansion : -expansion);
append(out, ClipperZUtils::to_zpaths<true>(expansion_cache, base_idx));
}
++ base_idx;
}
return out;
}
// Paths were created by splitting closed polygons into open paths and then by clipping them.
// Thus some pieces of the clipped polygons may now become split at the ends of the source polygons.
// Those ends are sorted lexicographically in "splits".
// Reconnect those split pieces.
static inline void merge_splits(ClipperLib_Z::Paths &paths, std::vector<std::pair<ClipperLib_Z::IntPoint, int>> &splits)
{
for (auto it_path = paths.begin(); it_path != paths.end(); ) {
ClipperLib_Z::Path &path = *it_path;
assert(path.size() >= 2);
bool merged = false;
if (path.size() >= 2) {
const ClipperLib_Z::IntPoint &front = path.front();
const ClipperLib_Z::IntPoint &back = path.back();
// The path before clipping was supposed to cross the clipping boundary or be fully out of it.
// Thus the clipped contour is supposed to become open, with one exception: The anchor expands into a closed hole.
if (front.x() != back.x() || front.y() != back.y()) {
// Look up the ends in "splits", possibly join the contours.
// "splits" maps into the other piece connected to the same end point.
auto find_end = [&splits](const ClipperLib_Z::IntPoint &pt) -> std::pair<ClipperLib_Z::IntPoint, int>* {
auto it = std::lower_bound(splits.begin(), splits.end(), pt,
[](const auto &l, const auto &r){ return ClipperZUtils::zpoint_lower(l.first, r); });
return it != splits.end() && it->first == pt ? &(*it) : nullptr;
};
auto *end = find_end(front);
bool end_front = true;
if (! end) {
end_front = false;
end = find_end(back);
}
if (end) {
// This segment ends at a split point of the source closed contour before clipping.
if (end->second == -1) {
// Open end was found, not matched yet.
end->second = int(it_path - paths.begin());
} else {
// Open end was found and matched with end->second
ClipperLib_Z::Path &other_path = paths[end->second];
polylines_merge(other_path, other_path.front() == end->first, std::move(path), end_front);
if (std::next(it_path) == paths.end()) {
paths.pop_back();
break;
}
path = std::move(paths.back());
paths.pop_back();
merged = true;
}
}
}
}
if (! merged)
++ it_path;
}
}
using AABBTreeBBoxes = AABBTreeIndirect::Tree<2, coord_t>;
static AABBTreeBBoxes build_aabb_tree_over_expolygons(const ExPolygons &expolygons)
{
// Calculate bounding boxes of internal slices.
std::vector<AABBTreeIndirect::BoundingBoxWrapper> bboxes;
bboxes.reserve(expolygons.size());
for (size_t i = 0; i < expolygons.size(); ++ i)
bboxes.emplace_back(i, get_extents(expolygons[i].contour));
// Build AABB tree over bounding boxes of boundary expolygons.
AABBTreeBBoxes out;
out.build_modify_input(bboxes);
return out;
}
static int sample_in_expolygons(
// AABB tree over boundary expolygons
const AABBTreeBBoxes &aabb_tree,
const ExPolygons &expolygons,
const Point &sample)
{
int out = -1;
AABBTreeIndirect::traverse(aabb_tree,
[&sample](const AABBTreeBBoxes::Node &node) {
return node.bbox.contains(sample);
},
[&expolygons, &sample, &out](const AABBTreeBBoxes::Node &node) {
assert(node.is_leaf());
assert(node.is_valid());
if (expolygons[node.idx].contains(sample)) {
out = int(node.idx);
// Stop traversal.
return false;
}
// Continue traversal.
return true;
});
return out;
}
std::vector<WaveSeed> wave_seeds(
// Source regions that are supposed to touch the boundary.
const ExPolygons &src,
// Boundaries of source regions touching the "boundary" regions will be expanded into the "boundary" region.
const ExPolygons &boundary,
// Initial expansion of src to make the source regions intersect with boundary regions just a bit.
float tiny_expansion,
// Sort output by boundary ID and source ID.
bool sorted)
{
assert(tiny_expansion > 0);
if (src.empty())
return {};
using Intersection = ClipperZUtils::ClipperZIntersectionVisitor::Intersection;
using Intersections = ClipperZUtils::ClipperZIntersectionVisitor::Intersections;
ClipperLib_Z::Paths segments;
Intersections intersections;
coord_t idx_boundary_begin = 1;
coord_t idx_boundary_end = idx_boundary_begin;
coord_t idx_src_end;
{
ClipperLib_Z::Clipper zclipper;
ClipperZUtils::ClipperZIntersectionVisitor visitor(intersections);
zclipper.ZFillFunction(visitor.clipper_callback());
// as closed contours
zclipper.AddPaths(ClipperZUtils::expolygons_to_zpaths(boundary, idx_boundary_end), ClipperLib_Z::ptClip, true);
// as open contours
std::vector<std::pair<ClipperLib_Z::IntPoint, int>> zsrc_splits;
{
idx_src_end = idx_boundary_end;
ClipperLib_Z::Paths zsrc = expolygons_to_zpaths_expanded_opened(src, tiny_expansion, idx_src_end);
zclipper.AddPaths(zsrc, ClipperLib_Z::ptSubject, false);
zsrc_splits.reserve(zsrc.size());
for (const ClipperLib_Z::Path &path : zsrc) {
assert(path.size() >= 2);
assert(path.front() == path.back());
zsrc_splits.emplace_back(path.front(), -1);
}
std::sort(zsrc_splits.begin(), zsrc_splits.end(), [](const auto &l, const auto &r){ return ClipperZUtils::zpoint_lower(l.first, r.first); });
}
ClipperLib_Z::PolyTree polytree;
zclipper.Execute(ClipperLib_Z::ctIntersection, polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
ClipperLib_Z::PolyTreeToPaths(std::move(polytree), segments);
merge_splits(segments, zsrc_splits);
}
// AABBTree over bounding boxes of boundaries.
// Only built if necessary, that is if any of the seed contours is closed, thus there is no intersection point
// with the boundary and all Z coordinates of the closed contour point to the source contour.
AABBTreeBBoxes aabb_tree;
// Sort paths into their respective islands.
// Each src x boundary will be processed (wave expanded) independently.
// Multiple pieces of a single src may intersect the same boundary.
WaveSeeds out;
out.reserve(segments.size());
int iseed = 0;
for (const ClipperLib_Z::Path &path : segments) {
assert(path.size() >= 2);
const ClipperLib_Z::IntPoint &front = path.front();
const ClipperLib_Z::IntPoint &back = path.back();
// Both ends of a seed segment are supposed to be inside a single boundary expolygon.
// Thus as long as the seed contour is not closed, it should be open at a boundary point.
assert((front == back && front.z() >= idx_boundary_end && front.z() < idx_src_end) ||
//(front.z() < 0 && back.z() < 0));
// Hope that at least one end of an open polyline is clipped by the boundary, thus an intersection point is created.
(front.z() < 0 || back.z() < 0));
const Intersection *intersection = nullptr;
auto intersection_point_valid = [idx_boundary_end, idx_src_end](const Intersection &is) {
return is.first >= 1 && is.first < idx_boundary_end &&
is.second >= idx_boundary_end && is.second < idx_src_end;
};
if (front.z() < 0) {
const Intersection &is = intersections[- front.z() - 1];
assert(intersection_point_valid(is));
if (intersection_point_valid(is))
intersection = &is;
}
if (! intersection && back.z() < 0) {
const Intersection &is = intersections[- back.z() - 1];
assert(intersection_point_valid(is));
if (intersection_point_valid(is))
intersection = &is;
}
if (intersection) {
// The path intersects the boundary contour at least at one side.
out.push_back({ uint32_t(intersection->second - idx_boundary_end), uint32_t(intersection->first - 1), ClipperZUtils::from_zpath(path) });
} else {
// This should be a closed contour.
assert(front == back && front.z() >= idx_boundary_end && front.z() < idx_src_end);
// Find a source boundary expolygon of one sample of this closed path.
if (aabb_tree.empty())
aabb_tree = build_aabb_tree_over_expolygons(boundary);
int boundary_id = sample_in_expolygons(aabb_tree, boundary, Point(front.x(), front.y()));
// Boundary that contains the sample point was found.
assert(boundary_id >= 0);
if (boundary_id >= 0)
out.push_back({ uint32_t(front.z() - idx_boundary_end), uint32_t(boundary_id), ClipperZUtils::from_zpath(path) });
}
++ iseed;
}
if (sorted)
// Sort the seeds by their intersection boundary and source contour.
std::sort(out.begin(), out.end(), lower_by_boundary_and_src);
return out;
}
static ClipperLib::Paths wavefront_initial(ClipperLib::ClipperOffset &co, const ClipperLib::Paths &polylines, float offset)
{
ClipperLib::Paths out;
out.reserve(polylines.size());
ClipperLib::Paths out_this;
for (const ClipperLib::Path &path : polylines) {
assert(path.size() >= 2);
co.Clear();
co.AddPath(path, jtRound, path.front() == path.back() ? ClipperLib::etClosedLine : ClipperLib::etOpenRound);
co.Execute(out_this, offset);
append(out, std::move(out_this));
}
return out;
}
// Input polygons may consist of multiple expolygons, even nested expolygons.
// After inflation some polygons may thus overlap, however the overlap is being resolved during the successive
// clipping operation, thus it is not being done here.
static ClipperLib::Paths wavefront_step(ClipperLib::ClipperOffset &co, const ClipperLib::Paths &polygons, float offset)
{
ClipperLib::Paths out;
out.reserve(polygons.size());
ClipperLib::Paths out_this;
for (const ClipperLib::Path &polygon : polygons) {
co.Clear();
// Execute reorients the contours so that the outer most contour has a positive area. Thus the output
// contours will be CCW oriented even though the input paths are CW oriented.
// Offset is applied after contour reorientation, thus the signum of the offset value is reversed.
co.AddPath(polygon, jtRound, ClipperLib::etClosedPolygon);
bool ccw = ClipperLib::Orientation(polygon);
co.Execute(out_this, ccw ? offset : - offset);
if (! ccw) {
// Reverse the resulting contours.
for (ClipperLib::Path &path : out_this)
std::reverse(path.begin(), path.end());
}
append(out, std::move(out_this));
}
return out;
}
static ClipperLib::Paths wavefront_clip(const ClipperLib::Paths &wavefront, const Polygons &clipping)
{
ClipperLib::Clipper clipper;
clipper.AddPaths(wavefront, ClipperLib::ptSubject, true);
clipper.AddPaths(ClipperUtils::PolygonsProvider(clipping), ClipperLib::ptClip, true);
ClipperLib::Paths out;
clipper.Execute(ClipperLib::ctIntersection, out, ClipperLib::pftPositive, ClipperLib::pftPositive);
return out;
}
static Polygons propagate_wave_from_boundary(
ClipperLib::ClipperOffset &co,
// Seed of the wave: Open polylines very close to the boundary.
const ClipperLib::Paths &seed,
// Boundary inside which the waveform will propagate.
const ExPolygon &boundary,
// How much to inflate the seed lines to produce the first wave area.
const float initial_step,
// How much to inflate the first wave area and the successive wave areas in each step.
const float other_step,
// Number of inflate steps after the initial step.
const size_t num_other_steps,
// Maximum inflation of seed contours over the boundary. Used to trim boundary to speed up
// clipping during wave propagation.
const float max_inflation)
{
assert(! seed.empty() && seed.front().size() >= 2);
Polygons clipping = ClipperUtils::clip_clipper_polygons_with_subject_bbox(boundary, get_extents<true>(seed).inflated(max_inflation));
ClipperLib::Paths polygons = wavefront_clip(wavefront_initial(co, seed, initial_step), clipping);
// Now offset the remaining
for (size_t ioffset = 0; ioffset < num_other_steps; ++ ioffset)
polygons = wavefront_clip(wavefront_step(co, polygons, other_step), clipping);
return to_polygons(polygons);
}
// Resulting regions are sorted by boundary id and source id.
std::vector<RegionExpansion> propagate_waves(const WaveSeeds &seeds, const ExPolygons &boundary, const RegionExpansionParameters &params)
{
std::vector<RegionExpansion> out;
ClipperLib::Paths paths;
ClipperLib::ClipperOffset co;
co.ArcTolerance = params.arc_tolerance;
co.ShortestEdgeLength = params.shortest_edge_length;
for (auto it_seed = seeds.begin(); it_seed != seeds.end();) {
auto it = it_seed;
paths.clear();
for (; it != seeds.end() && it->boundary == it_seed->boundary && it->src == it_seed->src; ++ it)
paths.emplace_back(it->path);
// Propagate the wavefront while clipping it with the trimmed boundary.
// Collect the expanded polygons, merge them with the source polygons.
RegionExpansion re;
for (Polygon &polygon : propagate_wave_from_boundary(co, paths, boundary[it_seed->boundary], params.initial_step, params.other_step, params.num_other_steps, params.max_inflation))
out.push_back({ std::move(polygon), it_seed->src, it_seed->boundary });
it_seed = it;
}
return out;
}
std::vector<RegionExpansion> propagate_waves(const ExPolygons &src, const ExPolygons &boundary, const RegionExpansionParameters &params)
{
return propagate_waves(wave_seeds(src, boundary, params.tiny_expansion, true), boundary, params);
}
std::vector<RegionExpansion> propagate_waves(const ExPolygons &src, const ExPolygons &boundary,
// Scaled expansion value
float expansion,
// Expand by waves of expansion_step size (expansion_step is scaled).
float expansion_step,
// Don't take more than max_nr_steps for small expansion_step.
size_t max_nr_steps)
{
return propagate_waves(src, boundary, RegionExpansionParameters::build(expansion, expansion_step, max_nr_steps));
}
// Returns regions per source ExPolygon expanded into boundary.
std::vector<RegionExpansionEx> propagate_waves_ex(const WaveSeeds &seeds, const ExPolygons &boundary, const RegionExpansionParameters &params)
{
std::vector<RegionExpansion> expanded = propagate_waves(seeds, boundary, params);
assert(std::is_sorted(seeds.begin(), seeds.end(), [](const auto &l, const auto &r){ return l.boundary < r.boundary || (l.boundary == r.boundary && l.src < r.src); }));
Polygons acc;
std::vector<RegionExpansionEx> out;
for (auto it = expanded.begin(); it != expanded.end(); ) {
auto it2 = it;
acc.clear();
for (; it2 != expanded.end() && it2->boundary_id == it->boundary_id && it2->src_id == it->src_id; ++ it2)
acc.emplace_back(std::move(it2->polygon));
size_t size = it2 - it;
if (size == 1)
out.push_back({ ExPolygon{std::move(acc.front())}, it->src_id, it->boundary_id });
else {
ExPolygons expolys = union_ex(acc);
reserve_more_power_of_2(out, expolys.size());
for (ExPolygon &ex : expolys)
out.push_back({ std::move(ex), it->src_id, it->boundary_id });
}
it = it2;
}
return out;
}
// Returns regions per source ExPolygon expanded into boundary.
std::vector<RegionExpansionEx> propagate_waves_ex(
// Source regions that are supposed to touch the boundary.
// Boundaries of source regions touching the "boundary" regions will be expanded into the "boundary" region.
const ExPolygons &src,
const ExPolygons &boundary,
// Scaled expansion value
float full_expansion,
// Expand by waves of expansion_step size (expansion_step is scaled).
float expansion_step,
// Don't take more than max_nr_steps for small expansion_step.
size_t max_nr_expansion_steps)
{
auto params = RegionExpansionParameters::build(full_expansion, expansion_step, max_nr_expansion_steps);
return propagate_waves_ex(wave_seeds(src, boundary, params.tiny_expansion, true), boundary, params);
}
std::vector<Polygons> expand_expolygons(const ExPolygons &src, const ExPolygons &boundary,
// Scaled expansion value
float expansion,
// Expand by waves of expansion_step size (expansion_step is scaled).
float expansion_step,
// Don't take more than max_nr_steps for small expansion_step.
size_t max_nr_steps)
{
std::vector<Polygons> out(src.size(), Polygons{});
for (RegionExpansion &r : propagate_waves(src, boundary, expansion, expansion_step, max_nr_steps))
out[r.src_id].emplace_back(std::move(r.polygon));
return out;
}
std::vector<ExPolygon> expand_merge_expolygons(ExPolygons &&src, const ExPolygons &boundary, const RegionExpansionParameters &params)
{
// expanded regions are sorted by boundary id and source id
std::vector<RegionExpansion> expanded = propagate_waves(src, boundary, params);
// expanded regions will be merged into source regions, thus they will be re-sorted by source id.
std::sort(expanded.begin(), expanded.end(), [](const auto &l, const auto &r) { return l.src_id < r.src_id; });
uint32_t last = 0;
Polygons acc;
ExPolygons out;
out.reserve(src.size());
for (auto it = expanded.begin(); it != expanded.end();) {
for (; last < it->src_id; ++ last)
out.emplace_back(std::move(src[last]));
acc.clear();
assert(it->src_id == last);
for (; it != expanded.end() && it->src_id == last; ++ it)
acc.emplace_back(std::move(it->polygon));
//FIXME offset & merging could be more efficient, for example one does not need to copy the source expolygon
ExPolygon &src_ex = src[last ++];
assert(! src_ex.contour.empty());
#if 0
{
static int iRun = 0;
BoundingBox bbox = get_extents(acc);
bbox.merge(get_extents(src_ex));
SVG svg(debug_out_path("expand_merge_expolygons-failed-union=%d.svg", iRun ++).c_str(), bbox);
svg.draw(acc);
svg.draw_outline(acc, "black", scale_(0.05));
svg.draw(src_ex, "red");
svg.Close();
}
#endif
Point sample = src_ex.contour.front();
append(acc, to_polygons(std::move(src_ex)));
ExPolygons merged = union_safety_offset_ex(acc);
// Expanding one expolygon by waves should not change connectivity of the source expolygon:
// Single expolygon should be produced possibly with increased number of holes.
if (merged.size() > 1) {
// assert(merged.size() == 1);
// There is something wrong with the initial waves. Most likely the bridge was not valid at all
// or the boundary region was very close to some bridge edge, but not really touching.
// Pick only a single merged expolygon, which contains one sample point of the source expolygon.
auto aabb_tree = build_aabb_tree_over_expolygons(merged);
int id = sample_in_expolygons(aabb_tree, merged, sample);
assert(id != -1);
if (id != -1)
out.emplace_back(std::move(merged[id]));
} else if (merged.size() == 1)
out.emplace_back(std::move(merged.front()));
}
for (; last < uint32_t(src.size()); ++ last)
out.emplace_back(std::move(src[last]));
return out;
}
} // Algorithm
} // Slic3r

View File

@ -0,0 +1,114 @@
#ifndef SRC_LIBSLIC3R_ALGORITHM_REGION_EXPANSION_HPP_
#define SRC_LIBSLIC3R_ALGORITHM_REGION_EXPANSION_HPP_
#include <cstdint>
#include <libslic3r/Point.hpp>
#include <libslic3r/Polygon.hpp>
#include <libslic3r/ExPolygon.hpp>
namespace Slic3r {
namespace Algorithm {
struct RegionExpansionParameters
{
// Initial expansion of src to make the source regions intersect with boundary regions just a bit.
float tiny_expansion;
// How much to inflate the seed lines to produce the first wave area.
float initial_step;
// How much to inflate the first wave area and the successive wave areas in each step.
float other_step;
// Number of inflate steps after the initial step.
size_t num_other_steps;
// Maximum inflation of seed contours over the boundary. Used to trim boundary to speed up
// clipping during wave propagation.
float max_inflation;
// Accuracy of the offsetter for wave propagation.
double arc_tolerance;
double shortest_edge_length;
static RegionExpansionParameters build(
// Scaled expansion value
float full_expansion,
// Expand by waves of expansion_step size (expansion_step is scaled).
float expansion_step,
// Don't take more than max_nr_steps for small expansion_step.
size_t max_nr_expansion_steps);
};
struct WaveSeed {
uint32_t src;
uint32_t boundary;
Points path;
};
using WaveSeeds = std::vector<WaveSeed>;
inline bool lower_by_boundary_and_src(const WaveSeed &l, const WaveSeed &r)
{
return l.boundary < r.boundary || (l.boundary == r.boundary && l.src < r.src);
}
inline bool lower_by_src_and_boundary(const WaveSeed &l, const WaveSeed &r)
{
return l.src < r.src || (l.src == r.src && l.boundary < r.boundary);
}
// Expand src slightly outwards to intersect boundaries, trim the offsetted src polylines by the boundaries.
// Return the trimmed paths annotated with their origin (source of the path, index of the boundary region).
WaveSeeds wave_seeds(
// Source regions that are supposed to touch the boundary.
const ExPolygons &src,
// Boundaries of source regions touching the "boundary" regions will be expanded into the "boundary" region.
const ExPolygons &boundary,
// Initial expansion of src to make the source regions intersect with boundary regions just a bit.
float tiny_expansion,
bool sorted);
struct RegionExpansion
{
Polygon polygon;
uint32_t src_id;
uint32_t boundary_id;
};
std::vector<RegionExpansion> propagate_waves(const WaveSeeds &seeds, const ExPolygons &boundary, const RegionExpansionParameters &params);
std::vector<RegionExpansion> propagate_waves(const ExPolygons &src, const ExPolygons &boundary,
// Scaled expansion value
float expansion,
// Expand by waves of expansion_step size (expansion_step is scaled).
float expansion_step,
// Don't take more than max_nr_steps for small expansion_step.
size_t max_nr_steps);
struct RegionExpansionEx
{
ExPolygon expolygon;
uint32_t src_id;
uint32_t boundary_id;
};
std::vector<RegionExpansionEx> propagate_waves_ex(const WaveSeeds &seeds, const ExPolygons &boundary, const RegionExpansionParameters &params);
std::vector<RegionExpansionEx> propagate_waves_ex(const ExPolygons &src, const ExPolygons &boundary,
// Scaled expansion value
float expansion,
// Expand by waves of expansion_step size (expansion_step is scaled).
float expansion_step,
// Don't take more than max_nr_steps for small expansion_step.
size_t max_nr_steps);
std::vector<Polygons> expand_expolygons(const ExPolygons &src, const ExPolygons &boundary,
// Scaled expansion value
float expansion,
// Expand by waves of expansion_step size (expansion_step is scaled).
float expansion_step,
// Don't take more than max_nr_steps for small expansion_step.
size_t max_nr_steps);
std::vector<ExPolygon> expand_merge_expolygons(ExPolygons &&src, const ExPolygons &boundary, const RegionExpansionParameters &params);
} // Algorithm
} // Slic3r
#endif /* SRC_LIBSLIC3R_ALGORITHM_REGION_EXPANSION_HPP_ */

View File

@ -85,7 +85,13 @@ template<class PConf>
void fill_config(PConf& pcfg, const ArrangeParams &params) {
// Align the arranged pile into the center of the bin
pcfg.alignment = PConf::Alignment::CENTER;
switch (params.alignment) {
case Pivots::Center: pcfg.alignment = PConf::Alignment::CENTER; break;
case Pivots::BottomLeft: pcfg.alignment = PConf::Alignment::BOTTOM_LEFT; break;
case Pivots::BottomRight: pcfg.alignment = PConf::Alignment::BOTTOM_RIGHT; break;
case Pivots::TopLeft: pcfg.alignment = PConf::Alignment::TOP_LEFT; break;
case Pivots::TopRight: pcfg.alignment = PConf::Alignment::TOP_RIGHT; break;
}
// Start placing the items from the center of the print bed
pcfg.starting_point = PConf::Alignment::CENTER;
@ -593,23 +599,27 @@ template<class Fn> auto call_with_bed(const Points &bed, Fn &&fn)
auto parea = poly_area(bed);
if ((1.0 - parea / area(bb)) < 1e-3)
return fn(bb);
return fn(RectangleBed{bb});
else if (!std::isnan(circ.radius()))
return fn(circ);
else
return fn(Polygon(bed));
return fn(IrregularBed{ExPolygon(bed)});
}
}
bool is_box(const Points &bed)
{
return !bed.empty() &&
((1.0 - poly_area(bed) / area(BoundingBox(bed))) < 1e-3);
}
template<>
void arrange(ArrangePolygons & items,
const ArrangePolygons &excludes,
const Points & bed,
const ArrangeParams & params)
{
call_with_bed(bed, [&](const auto &bin) {
arrange(items, excludes, bin, params);
});
arrange(items, excludes, to_arrange_bed(bed), params);
}
template<class BedT>
@ -649,5 +659,97 @@ template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, c
template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Polygon &bed, const ArrangeParams &params);
template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const InfiniteBed &bed, const ArrangeParams &params);
ArrangeBed to_arrange_bed(const Points &bedpts)
{
ArrangeBed ret;
call_with_bed(bedpts, [&](const auto &bed) {
ret = bed;
});
return ret;
}
void arrange(ArrangePolygons &items,
const ArrangePolygons &excludes,
const SegmentedRectangleBed &bed,
const ArrangeParams &params)
{
arrange(items, excludes, bed.bb, params);
if (! excludes.empty())
return;
auto it = std::max_element(items.begin(), items.end(),
[](auto &i1, auto &i2) {
return i1.bed_idx < i2.bed_idx;
});
size_t beds = 0;
if (it != items.end())
beds = it->bed_idx + 1;
std::vector<BoundingBox> pilebb(beds);
for (auto &itm : items) {
if (itm.bed_idx >= 0)
pilebb[itm.bed_idx].merge(get_extents(itm.transformed_poly()));
}
auto piecesz = unscaled(bed.bb).size();
piecesz.x() /= bed.segments.x();
piecesz.y() /= bed.segments.y();
for (size_t bedidx = 0; bedidx < beds; ++bedidx) {
BoundingBox bb;
auto pilesz = unscaled(pilebb[bedidx]).size();
bb.max.x() = scaled(std::ceil(pilesz.x() / piecesz.x()) * piecesz.x());
bb.max.y() = scaled(std::ceil(pilesz.y() / piecesz.y()) * piecesz.y());
switch (params.alignment) {
case Pivots::BottomLeft:
bb.translate(bed.bb.min - bb.min);
break;
case Pivots::TopRight:
bb.translate(bed.bb.max - bb.max);
break;
case Pivots::BottomRight: {
Point bedref{bed.bb.max.x(), bed.bb.min.y()};
Point bbref {bb.max.x(), bb.min.y()};
bb.translate(bedref - bbref);
break;
}
case Pivots::TopLeft: {
Point bedref{bed.bb.min.x(), bed.bb.max.y()};
Point bbref {bb.min.x(), bb.max.y()};
bb.translate(bedref - bbref);
break;
}
case Pivots::Center: {
bb.translate(bed.bb.center() - bb.center());
break;
}
}
Vec2crd d = bb.center() - pilebb[bedidx].center();
auto bedbb = bed.bb;
bedbb.offset(-params.min_bed_distance);
auto pilebbx = pilebb[bedidx];
pilebbx.translate(d);
Point corr{0, 0};
corr.x() = -std::min(0, pilebbx.min.x() - bedbb.min.x())
-std::max(0, pilebbx.max.x() - bedbb.max.x());
corr.y() = -std::min(0, pilebbx.min.y() - bedbb.min.y())
-std::max(0, pilebbx.max.y() - bedbb.max.y());
d += corr;
for (auto &itm : items)
if (itm.bed_idx == bedidx)
itm.translation += d;
}
}
} // namespace arr
} // namespace Slic3r

View File

@ -3,12 +3,25 @@
#include "ExPolygon.hpp"
#include <boost/variant.hpp>
#include <libslic3r/BoundingBox.hpp>
namespace Slic3r {
class BoundingBox;
namespace arrangement {
/// Representing an unbounded bed.
struct InfiniteBed {
Point center;
explicit InfiniteBed(const Point &p = {0, 0}): center{p} {}
};
struct RectangleBed {
BoundingBox bb;
};
/// A geometry abstraction for a circular print bed. Similarly to BoundingBox.
class CircleBed {
Point center_;
@ -22,12 +35,28 @@ public:
inline const Point& center() const { return center_; }
};
/// Representing an unbounded bed.
struct InfiniteBed {
Point center;
explicit InfiniteBed(const Point &p = {0, 0}): center{p} {}
struct SegmentedRectangleBed {
Vec<2, size_t> segments;
BoundingBox bb;
SegmentedRectangleBed (const BoundingBox &bb,
size_t segments_x,
size_t segments_y)
: segments{segments_x, segments_y}
, bb{bb}
{}
};
struct IrregularBed {
ExPolygon poly;
};
//enum BedType { Infinite, Rectangle, Circle, SegmentedRectangle, Irregular };
using ArrangeBed = boost::variant<InfiniteBed, RectangleBed, CircleBed, SegmentedRectangleBed, IrregularBed>;
ArrangeBed to_arrange_bed(const Points &bedpts);
/// A logical bed representing an object not being arranged. Either the arrange
/// has not yet successfully run on this ArrangePolygon or it could not fit the
/// object due to overly large size or invalid geometry.
@ -75,6 +104,10 @@ struct ArrangePolygon {
using ArrangePolygons = std::vector<ArrangePolygon>;
enum class Pivots {
Center, TopLeft, BottomLeft, BottomRight, TopRight
};
struct ArrangeParams {
/// The minimum distance which is allowed for any
@ -93,6 +126,12 @@ struct ArrangeParams {
bool allow_rotations = false;
/// Final alignment of the merged pile after arrangement
Pivots alignment = Pivots::Center;
/// Starting position hint for the arrangement
Pivots starting_point = Pivots::Center;
/// Progress indicator callback called when an object gets packed.
/// The unsigned argument is the number of items remaining to pack.
std::function<void(unsigned)> progressind;
@ -127,12 +166,32 @@ extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excl
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Polygon &bed, const ArrangeParams &params);
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const InfiniteBed &bed, const ArrangeParams &params);
inline void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const RectangleBed &bed, const ArrangeParams &params)
{
arrange(items, excludes, bed.bb, params);
}
inline void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const IrregularBed &bed, const ArrangeParams &params)
{
arrange(items, excludes, bed.poly.contour, params);
}
void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const SegmentedRectangleBed &bed, const ArrangeParams &params);
inline void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const ArrangeBed &bed, const ArrangeParams &params)
{
auto call_arrange = [&](const auto &realbed) { arrange(items, excludes, realbed, params); };
boost::apply_visitor(call_arrange, bed);
}
inline void arrange(ArrangePolygons &items, const Points &bed, const ArrangeParams &params = {}) { arrange(items, {}, bed, params); }
inline void arrange(ArrangePolygons &items, const BoundingBox &bed, const ArrangeParams &params = {}) { arrange(items, {}, bed, params); }
inline void arrange(ArrangePolygons &items, const CircleBed &bed, const ArrangeParams &params = {}) { arrange(items, {}, bed, params); }
inline void arrange(ArrangePolygons &items, const Polygon &bed, const ArrangeParams &params = {}) { arrange(items, {}, bed, params); }
inline void arrange(ArrangePolygons &items, const InfiniteBed &bed, const ArrangeParams &params = {}) { arrange(items, {}, bed, params); }
bool is_box(const Points &bed);
}} // namespace Slic3r::arrangement
#endif // MODELARRANGE_HPP

View File

@ -22,24 +22,9 @@ public:
BoundingBoxBase(const PointClass &p1, const PointClass &p2, const PointClass &p3) :
min(p1), max(p1), defined(false) { merge(p2); merge(p3); }
template<class It, class = IteratorOnly<It> >
BoundingBoxBase(It from, It to) : min(PointClass::Zero()), max(PointClass::Zero())
{
if (from == to) {
this->defined = false;
// throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBoxBase constructor");
} else {
auto it = from;
this->min = it->template cast<typename PointClass::Scalar>();
this->max = this->min;
for (++ it; it != to; ++ it) {
auto vec = it->template cast<typename PointClass::Scalar>();
this->min = this->min.cwiseMin(vec);
this->max = this->max.cwiseMax(vec);
}
this->defined = (this->min.x() < this->max.x()) && (this->min.y() < this->max.y());
}
}
template<class It, class = IteratorOnly<It>>
BoundingBoxBase(It from, It to)
{ construct(*this, from, to); }
BoundingBoxBase(const std::vector<PointClass> &points)
: BoundingBoxBase(points.begin(), points.end())
@ -70,6 +55,30 @@ public:
}
bool operator==(const BoundingBoxBase<PointClass> &rhs) { return this->min == rhs.min && this->max == rhs.max; }
bool operator!=(const BoundingBoxBase<PointClass> &rhs) { return ! (*this == rhs); }
private:
// to access construct()
friend BoundingBox get_extents<false>(const Points &pts);
friend BoundingBox get_extents<true>(const Points &pts);
// if IncludeBoundary, then a bounding box is defined even for a single point.
// otherwise a bounding box is only defined if it has a positive area.
// The output bounding box is expected to be set to "undefined" initially.
template<bool IncludeBoundary = false, class BoundingBoxType, class It, class = IteratorOnly<It>>
static void construct(BoundingBoxType &out, It from, It to)
{
if (from != to) {
auto it = from;
out.min = it->template cast<typename PointClass::Scalar>();
out.max = out.min;
for (++ it; it != to; ++ it) {
auto vec = it->template cast<typename PointClass::Scalar>();
out.min = out.min.cwiseMin(vec);
out.max = out.max.cwiseMax(vec);
}
out.defined = IncludeBoundary || (out.min.x() < out.max.x() && out.min.y() < out.max.y());
}
}
};
template <class PointClass>
@ -226,9 +235,18 @@ inline bool empty(const BoundingBox3Base<VT> &bb)
}
inline BoundingBox scaled(const BoundingBoxf &bb) { return {scaled(bb.min), scaled(bb.max)}; }
inline BoundingBox3 scaled(const BoundingBoxf3 &bb) { return {scaled(bb.min), scaled(bb.max)}; }
inline BoundingBoxf unscaled(const BoundingBox &bb) { return {unscaled(bb.min), unscaled(bb.max)}; }
inline BoundingBoxf3 unscaled(const BoundingBox3 &bb) { return {unscaled(bb.min), unscaled(bb.max)}; }
template<class T = coord_t>
BoundingBoxBase<Vec<2, T>> scaled(const BoundingBoxf &bb) { return {scaled<T>(bb.min), scaled<T>(bb.max)}; }
template<class T = coord_t>
BoundingBox3Base<Vec<3, T>> scaled(const BoundingBoxf3 &bb) { return {scaled<T>(bb.min), scaled<T>(bb.max)}; }
template<class T = double>
BoundingBoxBase<Vec<2, T>> unscaled(const BoundingBox &bb) { return {unscaled<T>(bb.min), unscaled<T>(bb.max)}; }
template<class T = double>
BoundingBox3Base<Vec<3, T>> unscaled(const BoundingBox3 &bb) { return {unscaled<T>(bb.min), unscaled<T>(bb.max)}; }
template<class Tout, class Tin>
auto cast(const BoundingBoxBase<Tin> &b)

View File

@ -75,12 +75,9 @@ private:
//return ideal bridge direction and unsupported bridge endpoints distance.
inline std::tuple<Vec2d, double> detect_bridging_direction(const Polygons &to_cover, const Polygons &anchors_area)
inline std::tuple<Vec2d, double> detect_bridging_direction(const Lines &floating_edges, const Polygons &overhang_area)
{
Polygons overhang_area = diff(to_cover, anchors_area);
Polylines floating_polylines = diff_pl(to_polylines(overhang_area), expand(anchors_area, float(SCALED_EPSILON)));
if (floating_polylines.empty()) {
if (floating_edges.empty()) {
// consider this area anchored from all sides, pick bridging direction that will likely yield shortest bridges
auto [pc1, pc2] = compute_principal_components(overhang_area);
if (pc2 == Vec2f::Zero()) { // overhang may be smaller than resolution. In this case, any direction is ok
@ -91,7 +88,6 @@ inline std::tuple<Vec2d, double> detect_bridging_direction(const Polygons &to_co
}
// Overhang is not fully surrounded by anchors, in that case, find such direction that will minimize the number of bridge ends/180turns in the air
Lines floating_edges = to_lines(floating_polylines);
std::unordered_map<double, Vec2d> directions{};
for (const Line &l : floating_edges) {
Vec2d normal = l.normal().cast<double>().normalized();
@ -125,6 +121,13 @@ inline std::tuple<Vec2d, double> detect_bridging_direction(const Polygons &to_co
return {result_dir, min_cost};
};
//return ideal bridge direction and unsupported bridge endpoints distance.
inline std::tuple<Vec2d, double> detect_bridging_direction(const Polygons &to_cover, const Polygons &anchors_area)
{
Polygons overhang_area = diff(to_cover, anchors_area);
Lines floating_edges = to_lines(diff_pl(to_polylines(overhang_area), expand(anchors_area, float(SCALED_EPSILON))));
return detect_bridging_direction(floating_edges, overhang_area);
}
}

View File

@ -641,7 +641,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
// perform operation
ClipperLib_Z::PolyTree loops_trimmed_tree;
clipper.Execute(ClipperLib_Z::ctDifference, loops_trimmed_tree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
ClipperLib_Z::PolyTreeToPaths(loops_trimmed_tree, loops_trimmed);
ClipperLib_Z::PolyTreeToPaths(std::move(loops_trimmed_tree), loops_trimmed);
}
// Third, produce the extrusions, sorted by the source loop indices.

View File

@ -22,6 +22,8 @@ set(SLIC3R_SOURCES
AABBTreeLines.hpp
AABBMesh.hpp
AABBMesh.cpp
Algorithm/RegionExpansion.cpp
Algorithm/RegionExpansion.hpp
AnyPtr.hpp
BoundingBox.cpp
BoundingBox.hpp
@ -36,6 +38,7 @@ set(SLIC3R_SOURCES
clipper.hpp
ClipperUtils.cpp
ClipperUtils.hpp
ClipperZUtils.hpp
Color.cpp
Color.hpp
Config.cpp
@ -79,6 +82,8 @@ set(SLIC3R_SOURCES
Fill/FillBase.hpp
Fill/FillConcentric.cpp
Fill/FillConcentric.hpp
Fill/FillEnsuring.cpp
Fill/FillEnsuring.hpp
Fill/FillHoneycomb.cpp
Fill/FillHoneycomb.hpp
Fill/FillGyroid.cpp

View File

@ -8,8 +8,6 @@
#include "SVG.hpp"
#endif /* CLIPPER_UTILS_DEBUG */
#define CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR (0.005f)
namespace Slic3r {
#ifdef CLIPPER_UTILS_DEBUG
@ -267,7 +265,7 @@ static ClipperLib::Paths raw_offset(PathsProvider &&paths, float offset, Clipper
co.ArcTolerance = miterLimit;
else
co.MiterLimit = miterLimit;
co.ShortestEdgeLength = double(std::abs(offset * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR));
co.ShortestEdgeLength = std::abs(offset * ClipperOffsetShortestEdgeFactor);
for (const ClipperLib::Path &path : paths) {
co.Clear();
// Execute reorients the contours so that the outer most contour has a positive area. Thus the output
@ -414,7 +412,7 @@ static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float d
co.ArcTolerance = miterLimit;
else
co.MiterLimit = miterLimit;
co.ShortestEdgeLength = double(std::abs(delta * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR));
co.ShortestEdgeLength = std::abs(delta * ClipperOffsetShortestEdgeFactor);
co.AddPath(expoly.contour.points, joinType, ClipperLib::etClosedPolygon);
co.Execute(contours, delta);
}
@ -435,7 +433,7 @@ static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float d
co.ArcTolerance = miterLimit;
else
co.MiterLimit = miterLimit;
co.ShortestEdgeLength = double(std::abs(delta * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR));
co.ShortestEdgeLength = std::abs(delta * ClipperOffsetShortestEdgeFactor);
co.AddPath(hole.points, joinType, ClipperLib::etClosedPolygon);
ClipperLib::Paths out2;
// Execute reorients the contours so that the outer most contour has a positive area. Thus the output
@ -720,6 +718,8 @@ Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfac
{ return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::SurfacesProvider(subject), ClipperUtils::SurfacesProvider(clip), do_safety_offset); }
Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
{ return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::SurfacesPtrProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); }
Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset)
{ return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::SurfacesPtrProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); }
Slic3r::ExPolygons intersection_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
{ return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); }
@ -1062,7 +1062,7 @@ ClipperLib::Path mittered_offset_path_scaled(const Points &contour, const std::v
};
// Minimum edge length, squared.
double lmin = *std::max_element(deltas.begin(), deltas.end()) * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR;
double lmin = *std::max_element(deltas.begin(), deltas.end()) * ClipperOffsetShortestEdgeFactor;
double l2min = lmin * lmin;
// Minimum angle to consider two edges to be parallel.
// Vojtech's estimate.

View File

@ -39,6 +39,9 @@ static constexpr const Slic3r::ClipperLib::JoinType DefaultLineJoinType = Sl
// Miter limit is ignored for jtSquare.
static constexpr const double DefaultLineMiterLimit = 0.;
// Decimation factor applied on input contour when doing offset, multiplied by the offset distance.
static constexpr const double ClipperOffsetShortestEdgeFactor = 0.005;
enum class ApplySafetyOffset {
No,
Yes
@ -370,6 +373,8 @@ inline Slic3r::Polygons shrink(const Slic3r::Polygons &polygons, const float d
{ assert(delta > 0); return offset(polygons, -delta, joinType, miterLimit); }
inline Slic3r::ExPolygons shrink_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ assert(delta > 0); return offset_ex(polygons, -delta, joinType, miterLimit); }
inline Slic3r::ExPolygons shrink_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ assert(delta > 0); return offset_ex(polygons, -delta, joinType, miterLimit); }
// Wherever applicable, please use the opening() / closing() variants instead, they convey their purpose better.
// Input polygons for negative offset shall be "normalized": There must be no overlap / intersections between the input polygons.
@ -430,6 +435,7 @@ Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::ExPoly
Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::Polylines diff_pl(const Slic3r::Polyline &subject, const Slic3r::Polygons &clip);
Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip);
Slic3r::Polylines diff_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygon &clip);
@ -601,6 +607,6 @@ Polygons variable_offset_outer(const ExPolygon &expoly, const std::vector<std::
ExPolygons variable_offset_outer_ex(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit = 2.);
ExPolygons variable_offset_inner_ex(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit = 2.);
}
} // namespace Slic3r
#endif
#endif // slic3r_ClipperUtils_hpp_

View File

@ -0,0 +1,143 @@
#ifndef slic3r_ClipperZUtils_hpp_
#define slic3r_ClipperZUtils_hpp_
#include <numeric>
#include <vector>
#include <clipper/clipper_z.hpp>
#include <libslic3r/Point.hpp>
namespace Slic3r {
namespace ClipperZUtils {
using ZPoint = ClipperLib_Z::IntPoint;
using ZPoints = ClipperLib_Z::Path;
using ZPath = ClipperLib_Z::Path;
using ZPaths = ClipperLib_Z::Paths;
inline bool zpoint_lower(const ZPoint &l, const ZPoint &r)
{
return l.x() < r.x() || (l.x() == r.x() && (l.y() < r.y() || (l.y() == r.y() && l.z() < r.z())));
}
// Convert a single path to path with a given Z coordinate.
// If Open, then duplicate the first point at the end.
template<bool Open = false>
inline ZPath to_zpath(const Points &path, coord_t z)
{
ZPath out;
if (! path.empty()) {
out.reserve((path.size() + Open) ? 1 : 0);
for (const Point &p : path)
out.emplace_back(p.x(), p.y(), z);
if (Open)
out.emplace_back(out.front());
}
return out;
}
// Convert multiple paths to paths with a given Z coordinate.
// If Open, then duplicate the first point of each path at its end.
template<bool Open = false>
inline ZPaths to_zpaths(const std::vector<Points> &paths, coord_t z)
{
ZPaths out;
out.reserve(paths.size());
for (const Points &path : paths)
out.emplace_back(to_zpath<Open>(path, z));
return out;
}
// Convert multiple expolygons into z-paths with Z specified by an index of the source expolygon
// offsetted by base_index.
// If Open, then duplicate the first point of each path at its end.
template<bool Open = false>
inline ZPaths expolygons_to_zpaths(const ExPolygons &src, coord_t &base_idx)
{
ZPaths out;
out.reserve(std::accumulate(src.begin(), src.end(), size_t(0),
[](const size_t acc, const ExPolygon &expoly) { return acc + expoly.num_contours(); }));
for (const ExPolygon &expoly : src) {
out.emplace_back(to_zpath<Open>(expoly.contour.points, base_idx));
for (const Polygon &hole : expoly.holes)
out.emplace_back(to_zpath<Open>(hole.points, base_idx));
++ base_idx;
}
return out;
}
// Convert a single path to path with a given Z coordinate.
// If Open, then duplicate the first point at the end.
template<bool Open = false>
inline Points from_zpath(const ZPoints &path)
{
Points out;
if (! path.empty()) {
out.reserve((path.size() + Open) ? 1 : 0);
for (const ZPoint &p : path)
out.emplace_back(p.x(), p.y());
if (Open)
out.emplace_back(out.front());
}
return out;
}
// Convert multiple paths to paths with a given Z coordinate.
// If Open, then duplicate the first point of each path at its end.
template<bool Open = false>
inline void from_zpaths(const ZPaths &paths, std::vector<Points> &out)
{
out.reserve(out.size() + paths.size());
for (const ZPoints &path : paths)
out.emplace_back(from_zpath<Open>(path));
}
template<bool Open = false>
inline std::vector<Points> from_zpaths(const ZPaths &paths)
{
std::vector<Points> out;
from_zpaths(paths, out);
return out;
}
class ClipperZIntersectionVisitor {
public:
using Intersection = std::pair<coord_t, coord_t>;
using Intersections = std::vector<Intersection>;
ClipperZIntersectionVisitor(Intersections &intersections) : m_intersections(intersections) {}
void reset() { m_intersections.clear(); }
void operator()(const ZPoint &e1bot, const ZPoint &e1top, const ZPoint &e2bot, const ZPoint &e2top, ZPoint &pt) {
coord_t srcs[4]{ e1bot.z(), e1top.z(), e2bot.z(), e2top.z() };
coord_t *begin = srcs;
coord_t *end = srcs + 4;
//FIXME bubble sort manually?
std::sort(begin, end);
end = std::unique(begin, end);
if (begin + 1 == end) {
// Self intersection may happen on source contour. Just copy the Z value.
pt.z() = *begin;
} else {
assert(begin + 2 == end);
if (begin + 2 <= end) {
// store a -1 based negative index into the "intersections" vector here.
m_intersections.emplace_back(srcs[0], srcs[1]);
pt.z() = -coord_t(m_intersections.size());
}
}
}
ClipperLib_Z::ZFillCallback clipper_callback() {
return [this](const ZPoint &e1bot, const ZPoint &e1top,
const ZPoint &e2bot, const ZPoint &e2top, ZPoint &pt)
{ return (*this)(e1bot, e1top, e2bot, e2top, pt); };
}
const std::vector<std::pair<coord_t, coord_t>>& intersections() const { return m_intersections; }
private:
std::vector<std::pair<coord_t, coord_t>> &m_intersections;
};
} // namespace ClipperZUtils
} // namespace Slic3r
#endif // slic3r_ClipperZUtils_hpp_

View File

@ -303,7 +303,7 @@ template <class T>
class ConfigOptionSingle : public ConfigOption {
public:
T value;
explicit ConfigOptionSingle(T value) : value(value) {}
explicit ConfigOptionSingle(T value) : value(std::move(value)) {}
operator T() const { return this->value; }
void set(const ConfigOption *rhs) override
@ -847,9 +847,9 @@ using ConfigOptionIntsNullable = ConfigOptionIntsTempl<true>;
class ConfigOptionString : public ConfigOptionSingle<std::string>
{
public:
ConfigOptionString() : ConfigOptionSingle<std::string>("") {}
explicit ConfigOptionString(const std::string &value) : ConfigOptionSingle<std::string>(value) {}
ConfigOptionString() : ConfigOptionSingle<std::string>(std::string{}) {}
explicit ConfigOptionString(std::string value) : ConfigOptionSingle<std::string>(std::move(value)) {}
static ConfigOptionType static_type() { return coString; }
ConfigOptionType type() const override { return static_type(); }
ConfigOption* clone() const override { return new ConfigOptionString(*this); }

View File

@ -1098,7 +1098,13 @@ namespace priv {
/// Track source of intersection
/// Help for anotate inner and outer faces
/// </summary>
struct Visitor {
struct Visitor : public CGAL::Polygon_mesh_processing::Corefinement::Default_visitor<CutMesh> {
Visitor(const CutMesh &object, const CutMesh &shape, EdgeShapeMap edge_shape_map,
FaceShapeMap face_shape_map, VertexShapeMap vert_shape_map, bool* is_valid) :
object(object), shape(shape), edge_shape_map(edge_shape_map), face_shape_map(face_shape_map),
vert_shape_map(vert_shape_map), is_valid(is_valid)
{}
const CutMesh &object;
const CutMesh &shape;
@ -1160,16 +1166,6 @@ struct Visitor {
/// <param name="v">New added vertex</param>
/// <param name="tm">Affected mesh</param>
void new_vertex_added(std::size_t i_id, VI v, const CutMesh &tm);
// Not used visitor functions
void before_subface_creations(FI /* f_old */, CutMesh &/* mesh */){}
void after_subface_created(FI /* f_new */, CutMesh &/* mesh */) {}
void after_subface_creations(CutMesh&) {}
void before_subface_created(CutMesh&) {}
void before_edge_split(HI /* h */, CutMesh& /* tm */) {}
void edge_split(HI /* hnew */, CutMesh& /* tm */) {}
void after_edge_split() {}
void add_retriangulation_edge(HI /* h */, CutMesh& /* tm */) {}
};
/// <summary>

View File

@ -15,10 +15,12 @@
#include "FillRectilinear.hpp"
#include "FillLightning.hpp"
#include "FillConcentric.hpp"
#include "FillEnsuring.hpp"
namespace Slic3r {
static constexpr const float NarrowInfillAreaThresholdMM = 3.f;
struct SurfaceFillParams
{
// Zero based extruder ID.
@ -159,7 +161,8 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
// Calculate the actual flow we'll be using for this infill.
params.bridge = is_bridge || Fill::use_bridge_flow(params.pattern);
params.flow = params.bridge ?
layerm.bridging_flow(extrusion_role) :
// Always enable thick bridges for internal bridges.
layerm.bridging_flow(extrusion_role, surface.is_bridge() && ! surface.is_external()) :
layerm.flow(extrusion_role, (surface.thickness == -1) ? layer.height : surface.thickness);
// Calculate flow spacing for infill pattern generation.
@ -302,6 +305,46 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
}
}
// Detect narrow internal solid infill area and use ipEnsuring pattern instead.
{
std::vector<char> narrow_expolygons;
static constexpr const auto narrow_pattern = ipEnsuring;
for (size_t surface_fill_id = 0, num_old_fills = surface_fills.size(); surface_fill_id < num_old_fills; ++ surface_fill_id)
if (SurfaceFill &fill = surface_fills[surface_fill_id]; fill.surface.surface_type == stInternalSolid) {
size_t num_expolygons = fill.expolygons.size();
narrow_expolygons.clear();
narrow_expolygons.reserve(num_expolygons);
// Detect narrow expolygons.
int num_narrow = 0;
for (const ExPolygon &ex : fill.expolygons) {
bool narrow = offset_ex(ex, -scaled<float>(NarrowInfillAreaThresholdMM)).empty();
num_narrow += int(narrow);
narrow_expolygons.emplace_back(narrow);
}
if (num_narrow == num_expolygons) {
// All expolygons are narrow, change the fill pattern.
fill.params.pattern = narrow_pattern;
} else if (num_narrow > 0) {
// Some expolygons are narrow, split the fills.
params = fill.params;
params.pattern = narrow_pattern;
surface_fills.emplace_back(params);
SurfaceFill &old_fill = surface_fills[surface_fill_id];
SurfaceFill &new_fill = surface_fills.back();
new_fill.region_id = old_fill.region_id;
new_fill.surface.surface_type = stInternalSolid;
new_fill.surface.thickness = old_fill.surface.thickness;
new_fill.expolygons.reserve(num_narrow);
for (size_t i = 0; i < narrow_expolygons.size(); ++ i)
if (narrow_expolygons[i])
new_fill.expolygons.emplace_back(std::move(old_fill.expolygons[i]));
old_fill.expolygons.erase(std::remove_if(old_fill.expolygons.begin(), old_fill.expolygons.end(),
[&narrow_expolygons, ex_first = old_fill.expolygons.data()](const ExPolygon& ex) { return narrow_expolygons[&ex - ex_first]; }),
old_fill.expolygons.end());
}
}
}
return surface_fills;
}
@ -441,14 +484,28 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
}
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
size_t first_object_layer_id = this->object()->get_layer(0)->id();
for (SurfaceFill &surface_fill : surface_fills) {
//skip patterns for which additional input is nullptr
switch (surface_fill.params.pattern) {
case ipLightning: if (lightning_generator == nullptr) continue; break;
case ipAdaptiveCubic: if (adaptive_fill_octree == nullptr) continue; break;
case ipSupportCubic: if (support_fill_octree == nullptr) continue; break;
default: break;
}
// Create the filler object.
std::unique_ptr<Fill> f = std::unique_ptr<Fill>(Fill::new_from_type(surface_fill.params.pattern));
f->set_bounding_box(bbox);
f->layer_id = this->id();
// Layer ID is used for orienting the infill in alternating directions.
// Layer::id() returns layer ID including raft layers, subtract them to make the infill direction independent
// from raft.
f->layer_id = this->id() - first_object_layer_id;
f->z = this->print_z;
f->angle = surface_fill.params.angle;
f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree;
f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree;
f->print_config = &this->object()->print()->config();
f->print_object_config = &this->object()->config();
if (surface_fill.params.pattern == ipLightning) {
auto *lf = dynamic_cast<FillLightning::Filler*>(f.get());
@ -456,11 +513,10 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
lf->num_raft_layers = this->object()->slicing_parameters().raft_layers();
}
if (perimeter_generator.value == PerimeterGeneratorType::Arachne && surface_fill.params.pattern == ipConcentric) {
FillConcentric *fill_concentric = dynamic_cast<FillConcentric *>(f.get());
assert(fill_concentric != nullptr);
fill_concentric->print_config = &this->object()->print()->config();
fill_concentric->print_object_config = &this->object()->config();
if (surface_fill.params.pattern == ipEnsuring) {
auto *fill_ensuring = dynamic_cast<FillEnsuring *>(f.get());
assert(fill_ensuring != nullptr);
fill_ensuring->print_region_config = &m_regions[surface_fill.region_id]->region().config();
}
// calculate flow spacing for infill pattern generation
@ -490,7 +546,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
params.anchor_length = surface_fill.params.anchor_length;
params.anchor_length_max = surface_fill.params.anchor_length_max;
params.resolution = resolution;
params.use_arachne = perimeter_generator == PerimeterGeneratorType::Arachne && surface_fill.params.pattern == ipConcentric;
params.use_arachne = (perimeter_generator == PerimeterGeneratorType::Arachne && surface_fill.params.pattern == ipConcentric) || surface_fill.params.pattern == ipEnsuring;
params.layer_height = layerm.layer()->height;
for (ExPolygon &expoly : surface_fill.expolygons) {
@ -591,6 +647,94 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
#endif
}
Polylines Layer::generate_sparse_infill_polylines_for_anchoring() const
{
std::vector<SurfaceFill> surface_fills = group_fills(*this);
const Slic3r::BoundingBox bbox = this->object()->bounding_box();
const auto resolution = this->object()->print()->config().gcode_resolution.value;
Polylines sparse_infill_polylines{};
for (SurfaceFill &surface_fill : surface_fills) {
// skip patterns for which additional input is nullptr
switch (surface_fill.params.pattern) {
case ipLightning: continue; break;
case ipAdaptiveCubic: continue; break;
case ipSupportCubic: continue; break;
case ipCount: continue; break;
case ipSupportBase: continue; break;
case ipEnsuring: continue; break;
case ipRectilinear:
case ipMonotonic:
case ipMonotonicLines:
case ipAlignedRectilinear:
case ipGrid:
case ipTriangles:
case ipStars:
case ipCubic:
case ipLine:
case ipConcentric:
case ipHoneycomb:
case ip3DHoneycomb:
case ipGyroid:
case ipHilbertCurve:
case ipArchimedeanChords:
case ipOctagramSpiral: break;
}
// Create the filler object.
std::unique_ptr<Fill> f = std::unique_ptr<Fill>(Fill::new_from_type(surface_fill.params.pattern));
f->set_bounding_box(bbox);
f->layer_id = this->id();
f->z = this->print_z;
f->angle = surface_fill.params.angle;
// f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree;
f->print_config = &this->object()->print()->config();
f->print_object_config = &this->object()->config();
// calculate flow spacing for infill pattern generation
double link_max_length = 0.;
if (!surface_fill.params.bridge) {
#if 0
link_max_length = layerm.region()->config().get_abs_value(surface.is_external() ? "external_fill_link_max_length" : "fill_link_max_length", flow.spacing());
// printf("flow spacing: %f, is_external: %d, link_max_length: %lf\n", flow.spacing(), int(surface.is_external()), link_max_length);
#else
if (surface_fill.params.density > 80.) // 80%
link_max_length = 3. * f->spacing;
#endif
}
// Maximum length of the perimeter segment linking two infill lines.
f->link_max_length = (coord_t) scale_(link_max_length);
// Used by the concentric infill pattern to clip the loops to create extrusion paths.
f->loop_clipping = coord_t(scale_(surface_fill.params.flow.nozzle_diameter()) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER);
LayerRegion &layerm = *m_regions[surface_fill.region_id];
// apply half spacing using this flow's own spacing and generate infill
FillParams params;
params.density = float(0.01 * surface_fill.params.density);
params.dont_adjust = false; // surface_fill.params.dont_adjust;
params.anchor_length = surface_fill.params.anchor_length;
params.anchor_length_max = surface_fill.params.anchor_length_max;
params.resolution = resolution;
params.use_arachne = false;
params.layer_height = layerm.layer()->height;
for (ExPolygon &expoly : surface_fill.expolygons) {
// Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon.
f->spacing = surface_fill.params.spacing;
surface_fill.surface.expolygon = std::move(expoly);
try {
Polylines polylines = f->fill_surface(&surface_fill.surface, params);
sparse_infill_polylines.insert(sparse_infill_polylines.end(), polylines.begin(), polylines.end());
} catch (InfillFailedException &) {}
}
}
return sparse_infill_polylines;
}
// Create ironing extrusions over top surfaces.
void Layer::make_ironing()
{
@ -698,7 +842,11 @@ void Layer::make_ironing()
FillRectilinear fill;
FillParams fill_params;
fill.set_bounding_box(this->object()->bounding_box());
fill.layer_id = this->id();
// Layer ID is used for orienting the infill in alternating directions.
// Layer::id() returns layer ID including raft layers, subtract them to make the infill direction independent
// from raft.
//FIXME ironing does not take fill angle into account. Shall it? Does it matter?
fill.layer_id = this->id() - this->object()->get_layer(0)->id();
fill.z = this->print_z;
fill.overlap = 0;
fill_params.density = 1.;

View File

@ -20,6 +20,7 @@
#include "FillRectilinear.hpp"
#include "FillAdaptive.hpp"
#include "FillLightning.hpp"
#include "FillEnsuring.hpp"
#include <boost/log/trivial.hpp>
@ -50,6 +51,7 @@ Fill* Fill::new_from_type(const InfillPattern type)
case ipSupportCubic: return new FillAdaptive::Filler();
case ipSupportBase: return new FillSupportBase();
case ipLightning: return new FillLightning::Filler();
case ipEnsuring: return new FillEnsuring();
default: throw Slic3r::InvalidArgument("unknown type");
}
}

View File

@ -91,6 +91,10 @@ public:
// Octree builds on mesh for usage in the adaptive cubic infill
FillAdaptive::Octree* adapt_fill_octree = nullptr;
// PrintConfig and PrintObjectConfig are used by infills that use Arachne (Concentric and FillEnsuring).
const PrintConfig *print_config = nullptr;
const PrintObjectConfig *print_object_config = nullptr;
public:
virtual ~Fill() {}
virtual Fill* clone() const = 0;

View File

@ -97,14 +97,8 @@ void FillConcentric::_fill_surface_single(const FillParams &params,
continue;
ThickPolyline thick_polyline = Arachne::to_thick_polyline(*extrusion);
if (extrusion->is_closed && thick_polyline.points.front() == thick_polyline.points.back() && thick_polyline.width.front() == thick_polyline.width.back()) {
thick_polyline.points.pop_back();
assert(thick_polyline.points.size() * 2 == thick_polyline.width.size());
int nearest_idx = nearest_point_index(thick_polyline.points, last_pos);
std::rotate(thick_polyline.points.begin(), thick_polyline.points.begin() + nearest_idx, thick_polyline.points.end());
std::rotate(thick_polyline.width.begin(), thick_polyline.width.begin() + 2 * nearest_idx, thick_polyline.width.end());
thick_polyline.points.emplace_back(thick_polyline.points.front());
}
if (extrusion->is_closed)
thick_polyline.start_at_index(nearest_point_index(thick_polyline.points, last_pos));
thick_polylines_out.emplace_back(std::move(thick_polyline));
last_pos = thick_polylines_out.back().last_point();
}

View File

@ -26,11 +26,6 @@ protected:
ThickPolylines &thick_polylines_out) override;
bool no_sort() const override { return true; }
const PrintConfig *print_config = nullptr;
const PrintObjectConfig *print_object_config = nullptr;
friend class Layer;
};
} // namespace Slic3r

View File

@ -0,0 +1,82 @@
#include "../ClipperUtils.hpp"
#include "../ShortestPath.hpp"
#include "../Arachne/WallToolPaths.hpp"
#include "FillEnsuring.hpp"
#include <boost/log/trivial.hpp>
namespace Slic3r {
ThickPolylines FillEnsuring::fill_surface_arachne(const Surface *surface, const FillParams &params)
{
assert(params.use_arachne);
assert(this->print_config != nullptr && this->print_object_config != nullptr && this->print_region_config != nullptr);
const coord_t scaled_spacing = scaled<coord_t>(this->spacing);
// Perform offset.
Slic3r::ExPolygons expp = this->overlap != 0. ? offset_ex(surface->expolygon, scaled<float>(this->overlap)) : ExPolygons{surface->expolygon};
// Create the infills for each of the regions.
ThickPolylines thick_polylines_out;
for (ExPolygon &ex_poly : expp) {
Point bbox_size = ex_poly.contour.bounding_box().size();
coord_t loops_count = std::max(bbox_size.x(), bbox_size.y()) / scaled_spacing + 1;
Polygons polygons = to_polygons(ex_poly);
Arachne::WallToolPaths wall_tool_paths(polygons, scaled_spacing, scaled_spacing, loops_count, 0, params.layer_height, *this->print_object_config, *this->print_config);
if (std::vector<Arachne::VariableWidthLines> loops = wall_tool_paths.getToolPaths(); !loops.empty()) {
std::vector<const Arachne::ExtrusionLine *> all_extrusions;
for (Arachne::VariableWidthLines &loop : loops) {
if (loop.empty())
continue;
for (const Arachne::ExtrusionLine &wall : loop)
all_extrusions.emplace_back(&wall);
}
// Split paths using a nearest neighbor search.
size_t firts_poly_idx = thick_polylines_out.size();
Point last_pos(0, 0);
for (const Arachne::ExtrusionLine *extrusion : all_extrusions) {
if (extrusion->empty())
continue;
ThickPolyline thick_polyline = Arachne::to_thick_polyline(*extrusion);
if (thick_polyline.length() == 0.)
//FIXME this should not happen.
continue;
assert(thick_polyline.size() > 1);
assert(thick_polyline.length() > 0.);
//assert(thick_polyline.points.size() == thick_polyline.width.size());
if (extrusion->is_closed)
thick_polyline.start_at_index(nearest_point_index(thick_polyline.points, last_pos));
assert(thick_polyline.size() > 1);
//assert(thick_polyline.points.size() == thick_polyline.width.size());
thick_polylines_out.emplace_back(std::move(thick_polyline));
last_pos = thick_polylines_out.back().last_point();
}
// clip the paths to prevent the extruder from getting exactly on the first point of the loop
// Keep valid paths only.
size_t j = firts_poly_idx;
for (size_t i = firts_poly_idx; i < thick_polylines_out.size(); ++i) {
assert(thick_polylines_out[i].size() > 1);
assert(thick_polylines_out[i].length() > 0.);
//assert(thick_polylines_out[i].points.size() == thick_polylines_out[i].width.size());
thick_polylines_out[i].clip_end(this->loop_clipping);
assert(thick_polylines_out[i].size() > 1);
if (thick_polylines_out[i].is_valid()) {
if (j < i)
thick_polylines_out[j] = std::move(thick_polylines_out[i]);
++j;
}
}
if (j < thick_polylines_out.size())
thick_polylines_out.erase(thick_polylines_out.begin() + int(j), thick_polylines_out.end());
}
}
return thick_polylines_out;
}
} // namespace Slic3r

View File

@ -0,0 +1,30 @@
#ifndef slic3r_FillEnsuring_hpp_
#define slic3r_FillEnsuring_hpp_
#include "FillBase.hpp"
#include "FillRectilinear.hpp"
namespace Slic3r {
class FillEnsuring : public FillRectilinear
{
public:
Fill *clone() const override { return new FillEnsuring(*this); }
~FillEnsuring() override = default;
Polylines fill_surface(const Surface *surface, const FillParams &params) override { return {}; };
ThickPolylines fill_surface_arachne(const Surface *surface, const FillParams &params) override;
protected:
void fill_surface_single_arachne(const Surface &surface, const FillParams &params, ThickPolylines &thick_polylines_out);
bool no_sort() const override { return true; }
// PrintRegionConfig is used for computing overlap between boundary contour and inner Rectilinear infill.
const PrintRegionConfig *print_region_config = nullptr;
friend class Layer;
};
} // namespace Slic3r
#endif // slic3r_FillEnsuring_hpp_

View File

@ -7,6 +7,7 @@
namespace Slic3r {
class PrintRegionConfig;
class Surface;
class FillRectilinear : public Fill

View File

@ -137,9 +137,7 @@ static constexpr const char* SOURCE_OFFSET_Y_KEY = "source_offset_y";
static constexpr const char* SOURCE_OFFSET_Z_KEY = "source_offset_z";
static constexpr const char* SOURCE_IN_INCHES_KEY = "source_in_inches";
static constexpr const char* SOURCE_IN_METERS_KEY = "source_in_meters";
#if ENABLE_RELOAD_FROM_DISK_REWORK
static constexpr const char* SOURCE_IS_BUILTIN_VOLUME_KEY = "source_is_builtin_volume";
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
static constexpr const char* MESH_STAT_EDGES_FIXED = "edges_fixed";
static constexpr const char* MESH_STAT_DEGENERATED_FACETS = "degenerate_facets";
@ -906,36 +904,18 @@ namespace Slic3r {
}
}
#if ENABLE_RELOAD_FROM_DISK_REWORK
for (int obj_id = 0; obj_id < int(model.objects.size()); ++obj_id) {
ModelObject* o = model.objects[obj_id];
for (int vol_id = 0; vol_id < int(o->volumes.size()); ++vol_id) {
ModelVolume* v = o->volumes[vol_id];
if (v->source.input_file.empty())
v->source.input_file = v->name.empty() ? filename : v->name;
v->source.input_file = filename;
if (v->source.volume_idx == -1)
v->source.volume_idx = vol_id;
if (v->source.object_idx == -1)
v->source.object_idx = obj_id;
}
}
#else
int object_idx = 0;
for (ModelObject* o : model.objects) {
int volume_idx = 0;
for (ModelVolume* v : o->volumes) {
if (v->source.input_file.empty() && v->type() == ModelVolumeType::MODEL_PART) {
v->source.input_file = filename;
if (v->source.volume_idx == -1)
v->source.volume_idx = volume_idx;
if (v->source.object_idx == -1)
v->source.object_idx = object_idx;
}
++volume_idx;
}
++object_idx;
}
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
// // fixes the min z of the model if negative
// model.adjust_min_z();
@ -2287,10 +2267,8 @@ namespace Slic3r {
volume->source.is_converted_from_inches = metadata.value == "1";
else if (metadata.key == SOURCE_IN_METERS_KEY)
volume->source.is_converted_from_meters = metadata.value == "1";
#if ENABLE_RELOAD_FROM_DISK_REWORK
else if (metadata.key == SOURCE_IS_BUILTIN_VOLUME_KEY)
volume->source.is_from_builtin_objects = metadata.value == "1";
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
else
volume->config.set_deserialize(metadata.key, metadata.value, config_substitutions);
}
@ -3328,10 +3306,8 @@ namespace Slic3r {
stream << prefix << SOURCE_IN_INCHES_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n";
else if (volume->source.is_converted_from_meters)
stream << prefix << SOURCE_IN_METERS_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n";
#if ENABLE_RELOAD_FROM_DISK_REWORK
if (volume->source.is_from_builtin_objects)
stream << prefix << SOURCE_IS_BUILTIN_VOLUME_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n";
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
}
// stores volume's config data

View File

@ -657,11 +657,7 @@ void AMFParserContext::endElement(const char * /* name */)
if (bool has_transform = !m_volume_transform.isApprox(Transform3d::Identity(), 1e-10); has_transform)
m_volume->source.transform = Slic3r::Geometry::Transformation(m_volume_transform);
#if ENABLE_RELOAD_FROM_DISK_REWORK
if (m_volume->source.input_file.empty()) {
#else
if (m_volume->source.input_file.empty() && m_volume->type() == ModelVolumeType::MODEL_PART) {
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
m_volume->source.object_idx = (int)m_model.objects.size() - 1;
m_volume->source.volume_idx = (int)m_model.objects.back()->volumes.size() - 1;
m_volume->center_geometry_after_creation();
@ -818,10 +814,8 @@ void AMFParserContext::endElement(const char * /* name */)
m_volume->source.is_converted_from_inches = m_value[1] == "1";
else if (strcmp(opt_key, "source_in_meters") == 0)
m_volume->source.is_converted_from_meters = m_value[1] == "1";
#if ENABLE_RELOAD_FROM_DISK_REWORK
else if (strcmp(opt_key, "source_is_builtin_volume") == 0)
m_volume->source.is_from_builtin_objects = m_value[1] == "1";
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
}
}
else if (m_path.size() == 3) {
@ -922,11 +916,7 @@ bool load_amf_file(const char *path, DynamicPrintConfig *config, ConfigSubstitut
unsigned int counter = 0;
for (ModelVolume* v : o->volumes) {
++counter;
#if ENABLE_RELOAD_FROM_DISK_REWORK
if (v->source.input_file.empty())
#else
if (v->source.input_file.empty() && v->type() == ModelVolumeType::MODEL_PART)
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
v->source.input_file = path;
if (v->name.empty()) {
v->name = o->name;
@ -1075,11 +1065,7 @@ bool load_amf_archive(const char* path, DynamicPrintConfig* config, ConfigSubsti
for (ModelObject *o : model->objects)
for (ModelVolume *v : o->volumes)
#if ENABLE_RELOAD_FROM_DISK_REWORK
if (v->source.input_file.empty())
#else
if (v->source.input_file.empty() && v->type() == ModelVolumeType::MODEL_PART)
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
v->source.input_file = path;
return true;
@ -1270,10 +1256,8 @@ bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config,
stream << " <metadata type=\"slic3r.source_in_inches\">1</metadata>\n";
else if (volume->source.is_converted_from_meters)
stream << " <metadata type=\"slic3r.source_in_meters\">1</metadata>\n";
#if ENABLE_RELOAD_FROM_DISK_REWORK
if (volume->source.is_from_builtin_objects)
stream << " <metadata type=\"slic3r.source_is_builtin_volume\">1</metadata>\n";
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
stream << std::setprecision(std::numeric_limits<float>::max_digits10);
const indexed_triangle_set &its = volume->mesh().its;
for (size_t i = 0; i < its.indices.size(); ++i) {

View File

@ -1,3 +1,4 @@
#include "Config.hpp"
#include "libslic3r.h"
#include "GCode/ExtrusionProcessor.hpp"
#include "I18N.hpp"
@ -24,6 +25,7 @@
#include <cstdlib>
#include <chrono>
#include <math.h>
#include <string>
#include <string_view>
#include <boost/algorithm/string.hpp>
@ -1003,18 +1005,6 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
std::sort(zs.begin(), zs.end());
m_layer_count += (unsigned int)(object->instances().size() * (std::unique(zs.begin(), zs.end()) - zs.begin()));
}
} else {
// Print all objects with the same print_z together.
std::vector<coordf_t> zs;
for (auto object : print.objects()) {
zs.reserve(zs.size() + object->layers().size() + object->support_layers().size());
for (auto layer : object->layers())
zs.push_back(layer->print_z);
for (auto layer : object->support_layers())
zs.push_back(layer->print_z);
}
std::sort(zs.begin(), zs.end());
m_layer_count = (unsigned int)(std::unique(zs.begin(), zs.end()) - zs.begin());
}
print.throw_if_canceled();
@ -1142,6 +1132,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
this->set_extruders(tool_ordering.all_extruders());
// Order object instances using a nearest neighbor search.
print_object_instances_ordering = chain_print_object_instances(print);
m_layer_count = tool_ordering.layer_tools().size();
}
if (initial_extruder_id == (unsigned int)-1) {
// Nothing to print!
@ -2867,16 +2858,36 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de
);
}
bool variable_speed = false;
bool variable_speed_or_fan_speed = false;
std::vector<ProcessedPoint> new_points{};
if (this->m_config.enable_dynamic_overhang_speeds && !this->on_first_layer() && path.role().is_perimeter()) {
if ((this->m_config.enable_dynamic_overhang_speeds || this->config().enable_dynamic_fan_speeds.get_at(m_writer.extruder()->id())) &&
!this->on_first_layer() && path.role().is_perimeter()) {
std::vector<std::pair<int, ConfigOptionFloatOrPercent>> overhangs_with_speeds = {{100, ConfigOptionFloatOrPercent{speed, false}}};
if (this->m_config.enable_dynamic_overhang_speeds) {
overhangs_with_speeds = {{0, m_config.overhang_speed_0},
{25, m_config.overhang_speed_1},
{50, m_config.overhang_speed_2},
{75, m_config.overhang_speed_3},
{100, ConfigOptionFloatOrPercent{speed, false}}};
}
std::vector<std::pair<int, ConfigOptionInts>> overhang_w_fan_speeds = {{100, ConfigOptionInts{0}}};
if (this->m_config.enable_dynamic_fan_speeds.get_at(m_writer.extruder()->id())) {
overhang_w_fan_speeds = {{0, m_config.overhang_fan_speed_0},
{25, m_config.overhang_fan_speed_1},
{50, m_config.overhang_fan_speed_2},
{75, m_config.overhang_fan_speed_3},
{100, ConfigOptionInts{0}}};
}
double external_perim_reference_speed = std::min(m_config.get_abs_value("external_perimeter_speed"),
std::min(EXTRUDER_CONFIG(filament_max_volumetric_speed) / path.mm3_per_mm,
m_config.max_volumetric_speed.value / path.mm3_per_mm));
new_points = m_extrusion_quality_estimator.estimate_extrusion_quality(path, m_config.overhang_overlap_levels,
m_config.dynamic_overhang_speeds,
external_perim_reference_speed, speed);
variable_speed = std::any_of(new_points.begin(), new_points.end(), [speed](const ProcessedPoint &p) { return p.speed != speed; });
new_points = m_extrusion_quality_estimator.estimate_extrusion_quality(path, overhangs_with_speeds, overhang_w_fan_speeds,
m_writer.extruder()->id(), external_perim_reference_speed,
speed);
variable_speed_or_fan_speed = std::any_of(new_points.begin(), new_points.end(),
[speed](const ProcessedPoint &p) { return p.speed != speed || p.fan_speed != 0; });
}
double F = speed * 60; // convert mm/sec to mm/min
@ -2932,7 +2943,8 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de
std::string cooling_marker_setspeed_comments;
if (m_enable_cooling_markers) {
if (path.role().is_bridge())
if (path.role().is_bridge() &&
(!path.role().is_perimeter() || !this->config().enable_dynamic_fan_speeds.get_at(m_writer.extruder()->id())))
gcode += ";_BRIDGE_FAN_START\n";
else
cooling_marker_setspeed_comments = ";_EXTRUDE_SET_SPEED";
@ -2940,7 +2952,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de
cooling_marker_setspeed_comments += ";_EXTERNAL_PERIMETER";
}
if (!variable_speed) {
if (!variable_speed_or_fan_speed) {
// F is mm per minute.
gcode += m_writer.set_speed(F, "", cooling_marker_setspeed_comments);
double path_length = 0.;
@ -2965,21 +2977,28 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de
marked_comment = description;
marked_comment += description_bridge;
}
double last_set_speed = new_points[0].speed * 60.0;
double last_set_speed = new_points[0].speed * 60.0;
double last_set_fan_speed = new_points[0].fan_speed;
gcode += m_writer.set_speed(last_set_speed, "", cooling_marker_setspeed_comments);
gcode += ";_SET_FAN_SPEED" + std::to_string(int(last_set_fan_speed)) + "\n";
Vec2d prev = this->point_to_gcode_quantized(new_points[0].p);
for (size_t i = 1; i < new_points.size(); i++) {
const ProcessedPoint& processed_point = new_points[i];
Vec2d p = this->point_to_gcode_quantized(processed_point.p);
const double line_length = (p - prev).norm();
const ProcessedPoint &processed_point = new_points[i];
Vec2d p = this->point_to_gcode_quantized(processed_point.p);
const double line_length = (p - prev).norm();
gcode += m_writer.extrude_to_xy(p, e_per_mm * line_length, marked_comment);
prev = p;
prev = p;
double new_speed = processed_point.speed * 60.0;
if (last_set_speed != new_speed) {
gcode += m_writer.set_speed(new_speed, "", cooling_marker_setspeed_comments);
last_set_speed = new_speed;
}
if (last_set_fan_speed != processed_point.fan_speed) {
last_set_fan_speed = processed_point.fan_speed;
gcode += ";_SET_FAN_SPEED" + std::to_string(int(last_set_fan_speed)) + "\n";
}
}
gcode += ";_RESET_FAN_SPEED\n";
}
if (m_enable_cooling_markers)

View File

@ -1,5 +1,6 @@
#include "../GCode.hpp"
#include "CoolingBuffer.hpp"
#include <algorithm>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/log/trivial.hpp>
@ -59,6 +60,9 @@ struct CoolingLine
// Would be TYPE_ADJUSTABLE, but the block of G-code lines has zero extrusion length, thus the block
// cannot have its speed adjusted. This should not happen (sic!).
TYPE_ADJUSTABLE_EMPTY = 1 << 12,
// Custom fan speed (introduced for overhang fan speed)
TYPE_SET_FAN_SPEED = 1 << 13,
TYPE_RESET_FAN_SPEED = 1 << 14,
};
CoolingLine(unsigned int type, size_t line_start, size_t line_end) :
@ -88,6 +92,8 @@ struct CoolingLine
float time;
// Maximum duration of this segment.
float time_max;
// Requested fan speed
int fan_speed;
// If marked with the "slowdown" flag, the line has been slowed down.
bool slowdown;
};
@ -372,7 +378,8 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') :
(*c == extrusion_axis) ? 3 : (*c == 'F') ? 4 : size_t(-1);
if (axis != size_t(-1)) {
auto [pend, ec] = fast_float::from_chars(&*(++ c), sline.data() + sline.size(), new_pos[axis]);
//auto [pend, ec] =
fast_float::from_chars(&*(++ c), sline.data() + sline.size(), new_pos[axis]);
if (axis == 4) {
// Convert mm/min to mm/sec.
new_pos[4] /= 60.f;
@ -491,13 +498,25 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
bool has_S = pos_S > 0;
bool has_P = pos_P > 0;
if (has_S || has_P) {
auto [pend, ec] = fast_float::from_chars(sline.data() + (has_S ? pos_S : pos_P) + 1, sline.data() + sline.size(), line.time);
//auto [pend, ec] =
fast_float::from_chars(sline.data() + (has_S ? pos_S : pos_P) + 1, sline.data() + sline.size(), line.time);
if (has_P)
line.time *= 0.001f;
} else
line.time = 0;
line.time_max = line.time;
} else if (boost::contains(sline, ";_SET_FAN_SPEED")) {
auto speed_start = sline.find_last_of('D');
int speed = 0;
for (char num : sline.substr(speed_start + 1)) {
speed = speed * 10 + (num - '0');
}
line.type = CoolingLine::TYPE_SET_FAN_SPEED;
line.fan_speed = speed;
} else if (boost::contains(sline, ";_RESET_FAN_SPEED")) {
line.type = CoolingLine::TYPE_RESET_FAN_SPEED;
}
if (line.type != 0)
adjustment->lines.emplace_back(std::move(line));
}
@ -730,10 +749,11 @@ std::string CoolingBuffer::apply_layer_cooldown(
new_gcode.reserve(gcode.size() * 2);
bool bridge_fan_control = false;
int bridge_fan_speed = 0;
auto change_extruder_set_fan = [ this, layer_id, layer_time, &new_gcode, &bridge_fan_control, &bridge_fan_speed ]() {
auto change_extruder_set_fan = [this, layer_id, layer_time, &new_gcode, &bridge_fan_control, &bridge_fan_speed]() {
#define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_current_extruder)
int min_fan_speed = EXTRUDER_CONFIG(min_fan_speed);
int fan_speed_new = EXTRUDER_CONFIG(fan_always_on) ? min_fan_speed : 0;
std::pair<int, int> custom_fan_speed_limits{fan_speed_new, 100 };
int disable_fan_first_layers = EXTRUDER_CONFIG(disable_fan_first_layers);
// Is the fan speed ramp enabled?
int full_fan_speed_layer = EXTRUDER_CONFIG(full_fan_speed_layer);
@ -750,11 +770,13 @@ std::string CoolingBuffer::apply_layer_cooldown(
if (layer_time < slowdown_below_layer_time) {
// Layer time very short. Enable the fan to a full throttle.
fan_speed_new = max_fan_speed;
custom_fan_speed_limits.first = fan_speed_new;
} else if (layer_time < fan_below_layer_time) {
// Layer time quite short. Enable the fan proportionally according to the current layer time.
assert(layer_time >= slowdown_below_layer_time);
double t = (layer_time - slowdown_below_layer_time) / (fan_below_layer_time - slowdown_below_layer_time);
fan_speed_new = int(floor(t * min_fan_speed + (1. - t) * max_fan_speed) + 0.5);
custom_fan_speed_limits.first = fan_speed_new;
}
}
bridge_fan_speed = EXTRUDER_CONFIG(bridge_fan_speed);
@ -763,6 +785,7 @@ std::string CoolingBuffer::apply_layer_cooldown(
float factor = float(int(layer_id + 1) - disable_fan_first_layers) / float(full_fan_speed_layer - disable_fan_first_layers);
fan_speed_new = std::clamp(int(float(fan_speed_new) * factor + 0.5f), 0, 255);
bridge_fan_speed = std::clamp(int(float(bridge_fan_speed) * factor + 0.5f), 0, 255);
custom_fan_speed_limits.second = fan_speed_new;
}
#undef EXTRUDER_CONFIG
bridge_fan_control = bridge_fan_speed > fan_speed_new;
@ -775,11 +798,13 @@ std::string CoolingBuffer::apply_layer_cooldown(
m_fan_speed = fan_speed_new;
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, m_fan_speed);
}
custom_fan_speed_limits.first = std::min(custom_fan_speed_limits.first, custom_fan_speed_limits.second);
return custom_fan_speed_limits;
};
const char *pos = gcode.c_str();
int current_feedrate = 0;
change_extruder_set_fan();
std::pair<int,int> fan_speed_limits = change_extruder_set_fan();
for (const CoolingLine *line : lines) {
const char *line_start = gcode.c_str() + line->line_start;
const char *line_end = gcode.c_str() + line->line_end;
@ -790,9 +815,17 @@ std::string CoolingBuffer::apply_layer_cooldown(
auto res = std::from_chars(line_start + m_toolchange_prefix.size(), line_end, new_extruder);
if (res.ec != std::errc::invalid_argument && new_extruder != m_current_extruder) {
m_current_extruder = new_extruder;
change_extruder_set_fan();
fan_speed_limits = change_extruder_set_fan();
}
new_gcode.append(line_start, line_end - line_start);
} else if (line->type & CoolingLine::TYPE_SET_FAN_SPEED) {
int new_speed = std::clamp(line->fan_speed, fan_speed_limits.first, fan_speed_limits.second);
if (m_fan_speed != new_speed) {
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, new_speed);
m_fan_speed = new_speed;
}
} else if (line->type & CoolingLine::TYPE_RESET_FAN_SPEED){
fan_speed_limits = change_extruder_set_fan();
} else if (line->type & CoolingLine::TYPE_BRIDGE_FAN_START) {
if (bridge_fan_control)
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, bridge_fan_speed);
@ -816,7 +849,8 @@ std::string CoolingBuffer::apply_layer_cooldown(
if (line->slowdown)
new_feedrate = int(floor(60. * line->feedrate + 0.5));
else
auto res = std::from_chars(fpos, line_end, new_feedrate);
//auto res =
std::from_chars(fpos, line_end, new_feedrate);
if (new_feedrate == current_feedrate) {
// No need to change the F value.
if ((line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_ADJUSTABLE_EMPTY | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) || line->length == 0.)

View File

@ -17,6 +17,7 @@
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <iterator>
#include <limits>
#include <numeric>
#include <unordered_map>
@ -238,6 +239,7 @@ struct ProcessedPoint
{
Point p;
float speed = 1.0f;
int fan_speed = 0;
};
class ExtrusionQualityEstimator
@ -257,34 +259,27 @@ public:
next_layer_boundaries[object] = AABBTreeLines::LinesDistancer<Linef>{to_unscaled_linesf(layer->lslices)};
}
std::vector<ProcessedPoint> estimate_extrusion_quality(const ExtrusionPath &path,
const ConfigOptionPercents &overlaps,
const ConfigOptionFloatsOrPercents &speeds,
float ext_perimeter_speed,
float original_speed)
std::vector<ProcessedPoint> estimate_extrusion_quality(const ExtrusionPath &path,
const std::vector<std::pair<int, ConfigOptionFloatOrPercent>> overhangs_w_speeds,
const std::vector<std::pair<int, ConfigOptionInts>> overhangs_w_fan_speeds,
size_t extruder_id,
float ext_perimeter_speed,
float original_speed)
{
size_t speed_sections_count = std::min(overlaps.values.size(), speeds.values.size());
float speed_base = ext_perimeter_speed > 0 ? ext_perimeter_speed : original_speed;
std::vector<std::pair<float, float>> speed_sections;
for (size_t i = 0; i < speed_sections_count; i++) {
float distance = path.width * (1.0 - (overlaps.get_at(i) / 100.0));
float speed = speeds.get_at(i).percent ? (speed_base * speeds.get_at(i).value / 100.0) : speeds.get_at(i).value;
speed_sections.push_back({distance, speed});
float speed_base = ext_perimeter_speed > 0 ? ext_perimeter_speed : original_speed;
std::map<float, float> speed_sections;
for (size_t i = 0; i < overhangs_w_speeds.size(); i++) {
float distance = path.width * (1.0 - (overhangs_w_speeds[i].first / 100.0));
float speed = overhangs_w_speeds[i].second.percent ? (speed_base * overhangs_w_speeds[i].second.value / 100.0) :
overhangs_w_speeds[i].second.value;
speed_sections[distance] = speed;
}
std::sort(speed_sections.begin(), speed_sections.end(),
[](const std::pair<float, float> &a, const std::pair<float, float> &b) {
if (a.first == b.first) {
return a.second > b.second;
}
return a.first < b.first; });
std::pair<float, float> last_section{INFINITY, 0};
for (auto &section : speed_sections) {
if (section.first == last_section.first) {
section.second = last_section.second;
} else {
last_section = section;
}
std::map<float, float> fan_speed_sections;
for (size_t i = 0; i < overhangs_w_fan_speeds.size(); i++) {
float distance = path.width * (1.0 - (overhangs_w_fan_speeds[i].first / 100.0));
float fan_speed = overhangs_w_fan_speeds[i].second.get_at(extruder_id);
fan_speed_sections[distance] = fan_speed;
}
std::vector<ExtendedPoint> extended_points =
@ -296,28 +291,26 @@ public:
const ExtendedPoint &curr = extended_points[i];
const ExtendedPoint &next = extended_points[i + 1 < extended_points.size() ? i + 1 : i];
auto calculate_speed = [&speed_sections, &original_speed](float distance) {
float final_speed;
if (distance <= speed_sections.front().first) {
final_speed = original_speed;
} else if (distance >= speed_sections.back().first) {
final_speed = speed_sections.back().second;
} else {
size_t section_idx = 0;
while (distance > speed_sections[section_idx + 1].first) {
section_idx++;
}
float t = (distance - speed_sections[section_idx].first) /
(speed_sections[section_idx + 1].first - speed_sections[section_idx].first);
t = std::clamp(t, 0.0f, 1.0f);
final_speed = (1.0f - t) * speed_sections[section_idx].second + t * speed_sections[section_idx + 1].second;
auto interpolate_speed = [](const std::map<float, float> &values, float distance) {
auto upper_dist = values.lower_bound(distance);
if (upper_dist == values.end()) {
return values.rbegin()->second;
}
return final_speed;
if (upper_dist == values.begin()) {
return upper_dist->second;
}
auto lower_dist = std::prev(upper_dist);
float t = (distance - lower_dist->first) / (upper_dist->first - lower_dist->first);
return (1.0f - t) * lower_dist->second + t * upper_dist->second;
};
float extrusion_speed = std::min(calculate_speed(curr.distance), calculate_speed(next.distance));
float extrusion_speed = std::min(interpolate_speed(speed_sections, curr.distance),
interpolate_speed(speed_sections, next.distance));
float fan_speed = std::min(interpolate_speed(fan_speed_sections, curr.distance),
interpolate_speed(fan_speed_sections, next.distance));
processed_points.push_back({scaled(curr.position), extrusion_speed});
processed_points.push_back({scaled(curr.position), extrusion_speed, int(fan_speed)});
}
return processed_points;
}

View File

@ -2402,7 +2402,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
if (m_forced_height > 0.0f)
m_height = m_forced_height;
else if (m_layer_id == 0)
m_height = std::min((float)m_end_position[Z], m_first_layer_height + m_z_offset);
m_height = m_first_layer_height + m_z_offset;
else if (line.comment() != INTERNAL_G2G3_TAG){
if (m_end_position[Z] > m_extruded_last_z + EPSILON && delta_pos[Z] == 0.0)
m_height = m_end_position[Z] - m_extruded_last_z;

View File

@ -19,20 +19,7 @@ bool RetractWhenCrossingPerimeters::travel_inside_internal_regions(const Layer &
if (surface.is_internal())
m_internal_islands.emplace_back(&surface.expolygon);
// Calculate bounding boxes of internal slices.
class BBoxWrapper {
public:
BBoxWrapper(const size_t idx, const BoundingBox &bbox) :
m_idx(idx),
// Inflate the bounding box a bit to account for numerical issues.
m_bbox(bbox.min - Point(SCALED_EPSILON, SCALED_EPSILON), bbox.max + Point(SCALED_EPSILON, SCALED_EPSILON)) {}
size_t idx() const { return m_idx; }
const AABBTree::BoundingBox& bbox() const { return m_bbox; }
Point centroid() const { return ((m_bbox.min().cast<int64_t>() + m_bbox.max().cast<int64_t>()) / 2).cast<int32_t>(); }
private:
size_t m_idx;
AABBTree::BoundingBox m_bbox;
};
std::vector<BBoxWrapper> bboxes;
std::vector<AABBTreeIndirect::BoundingBoxWrapper> bboxes;
bboxes.reserve(m_internal_islands.size());
for (size_t i = 0; i < m_internal_islands.size(); ++ i)
bboxes.emplace_back(i, get_extents(*m_internal_islands[i]));

View File

@ -468,9 +468,6 @@ void MedialAxis::build(ThickPolylines* polylines)
}
*/
//typedef const VD::vertex_type vert_t;
using edge_t = const VD::edge_type;
// collect valid edges (i.e. prune those not belonging to MAT)
// note: this keeps twins, so it inserts twice the number of the valid edges
m_edge_data.assign(m_vd.edges().size() / 2, EdgeData{});

View File

@ -1,5 +1,5 @@
#include "Layer.hpp"
#include <clipper/clipper_z.hpp>
#include "ClipperZUtils.hpp"
#include "ClipperUtils.hpp"
#include "Print.hpp"
#include "Fill/Fill.hpp"
@ -302,48 +302,16 @@ void Layer::build_up_down_graph(Layer& below, Layer& above)
coord_t paths_end = paths_above_offset + coord_t(above.lslices.size());
#endif // NDEBUG
class ZFill {
public:
ZFill() = default;
void reset() { m_intersections.clear(); }
void operator()(
const ClipperLib_Z::IntPoint& e1bot, const ClipperLib_Z::IntPoint& e1top,
const ClipperLib_Z::IntPoint& e2bot, const ClipperLib_Z::IntPoint& e2top,
ClipperLib_Z::IntPoint& pt) {
coord_t srcs[4]{ e1bot.z(), e1top.z(), e2bot.z(), e2top.z() };
coord_t* begin = srcs;
coord_t* end = srcs + 4;
std::sort(begin, end);
end = std::unique(begin, end);
if (begin + 1 == end) {
// Self intersection may happen on source contour. Just copy the Z value.
pt.z() = *begin;
} else {
assert(begin + 2 == end);
if (begin + 2 <= end) {
// store a -1 based negative index into the "intersections" vector here.
m_intersections.emplace_back(srcs[0], srcs[1]);
pt.z() = -coord_t(m_intersections.size());
}
}
}
const std::vector<std::pair<coord_t, coord_t>>& intersections() const { return m_intersections; }
private:
std::vector<std::pair<coord_t, coord_t>> m_intersections;
} zfill;
ClipperLib_Z::Clipper clipper;
ClipperLib_Z::PolyTree result;
clipper.ZFillFunction(
[&zfill](const ClipperLib_Z::IntPoint &e1bot, const ClipperLib_Z::IntPoint &e1top,
const ClipperLib_Z::IntPoint &e2bot, const ClipperLib_Z::IntPoint &e2top, ClipperLib_Z::IntPoint &pt)
{ return zfill(e1bot, e1top, e2bot, e2top, pt); });
ClipperZUtils::ClipperZIntersectionVisitor::Intersections intersections;
ClipperZUtils::ClipperZIntersectionVisitor visitor(intersections);
clipper.ZFillFunction(visitor.clipper_callback());
clipper.AddPaths(paths_below, ClipperLib_Z::ptSubject, true);
clipper.AddPaths(paths_above, ClipperLib_Z::ptClip, true);
clipper.Execute(ClipperLib_Z::ctIntersection, result, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
connect_layer_slices(below, above, result, zfill.intersections(), paths_below_offset, paths_above_offset
connect_layer_slices(below, above, result, intersections, paths_below_offset, paths_above_offset
#ifndef NDEBUG
, paths_end
#endif // NDEBUG

View File

@ -134,7 +134,7 @@ public:
Flow flow(FlowRole role) const;
Flow flow(FlowRole role, double layer_height) const;
Flow bridging_flow(FlowRole role) const;
Flow bridging_flow(FlowRole role, bool force_thick_bridges = false) const;
void slices_to_fill_surfaces_clipped();
void prepare_fill_surfaces();
@ -369,6 +369,7 @@ public:
// Phony version of make_fills() without parameters for Perl integration only.
void make_fills() { this->make_fills(nullptr, nullptr, nullptr); }
void make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator);
Polylines generate_sparse_infill_polylines_for_anchoring() const;
void make_ironing();
void export_region_slices_to_svg(const char *path) const;

View File

@ -8,6 +8,7 @@
#include "Surface.hpp"
#include "BoundingBox.hpp"
#include "SVG.hpp"
#include "Algorithm/RegionExpansion.hpp"
#include <string>
#include <map>
@ -26,12 +27,12 @@ Flow LayerRegion::flow(FlowRole role, double layer_height) const
return m_region->flow(*m_layer->object(), role, layer_height, m_layer->id() == 0);
}
Flow LayerRegion::bridging_flow(FlowRole role) const
Flow LayerRegion::bridging_flow(FlowRole role, bool force_thick_bridges) const
{
const PrintRegion &region = this->region();
const PrintRegionConfig &region_config = region.config();
const PrintObject &print_object = *this->layer()->object();
if (print_object.config().thick_bridges) {
if (print_object.config().thick_bridges || force_thick_bridges) {
// The old Slic3r way (different from all other slicers): Use rounded extrusions.
// Get the configured nozzle_diameter for the extruder associated to the flow role requested.
// Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right.
@ -139,6 +140,275 @@ void LayerRegion::make_perimeters(
}
}
#if 1
// Extract surfaces of given type from surfaces, extract fill (layer) thickness of one of the surfaces.
static ExPolygons fill_surfaces_extract_expolygons(Surfaces &surfaces, SurfaceType surface_type, double &thickness)
{
size_t cnt = 0;
for (const Surface &surface : surfaces)
if (surface.surface_type == surface_type) {
++ cnt;
thickness = surface.thickness;
}
if (cnt == 0)
return {};
ExPolygons out;
out.reserve(cnt);
for (Surface &surface : surfaces)
if (surface.surface_type == surface_type)
out.emplace_back(std::move(surface.expolygon));
return out;
}
// Extract bridging surfaces from "surfaces", expand them into "shells" using expansion_params,
// detect bridges.
// Trim "shells" by the expanded bridges.
Surfaces expand_bridges_detect_orientations(
Surfaces &surfaces,
ExPolygons &shells,
const Algorithm::RegionExpansionParameters &expansion_params)
{
using namespace Slic3r::Algorithm;
double thickness;
ExPolygons bridges_ex = fill_surfaces_extract_expolygons(surfaces, stBottomBridge, thickness);
if (bridges_ex.empty())
return {};
// Calculate bridge anchors and their expansions in their respective shell region.
WaveSeeds bridge_anchors = wave_seeds(bridges_ex, shells, expansion_params.tiny_expansion, true);
std::vector<RegionExpansionEx> bridge_expansions = propagate_waves_ex(bridge_anchors, shells, expansion_params);
// Cache for detecting bridge orientation and merging regions with overlapping expansions.
struct Bridge {
ExPolygon expolygon;
uint32_t group_id;
std::vector<RegionExpansionEx>::const_iterator bridge_expansion_begin;
double angle = -1;
};
std::vector<Bridge> bridges;
{
bridges.reserve(bridges_ex.size());
uint32_t group_id = 0;
for (ExPolygon &ex : bridges_ex)
bridges.push_back({ std::move(ex), group_id ++, bridge_expansions.end() });
bridges_ex.clear();
}
// Group the bridge surfaces by overlaps.
auto group_id = [&bridges](uint32_t src_id) {
uint32_t group_id = bridges[src_id].group_id;
while (group_id != src_id) {
src_id = group_id;
group_id = bridges[src_id].group_id;
}
bridges[src_id].group_id = group_id;
return group_id;
};
{
// Cache of bboxes per expansion boundary.
std::vector<BoundingBox> bboxes;
// Detect overlaps of bridge anchors inside their respective shell regions.
// bridge_expansions are sorted by boundary id and source id.
for (auto it = bridge_expansions.begin(); it != bridge_expansions.end();) {
// For each boundary region:
auto it_begin = it;
auto it_end = std::next(it_begin);
for (; it_end != bridge_expansions.end() && it_end->boundary_id == it_begin->boundary_id; ++ it_end) ;
bboxes.clear();
bboxes.reserve(it_end - it_begin);
for (auto it2 = it_begin; it2 != it_end; ++ it2)
bboxes.emplace_back(get_extents(it2->expolygon.contour));
// For each bridge anchor of the current source:
for (; it != it_end; ++ it) {
// A grup id for this bridge.
for (auto it2 = std::next(it); it2 != it_end; ++ it2)
if (it->src_id != it2->src_id &&
bboxes[it - it_begin].overlap(bboxes[it2 - it_begin]) &&
// One may ignore holes, they are irrelevant for intersection test.
! intersection(it->expolygon.contour, it2->expolygon.contour).empty()) {
// The two bridge regions intersect. Give them the same group id.
uint32_t id = group_id(it->src_id);
uint32_t id2 = group_id(it2->src_id);
bridges[it->src_id].group_id = bridges[it2->src_id].group_id = std::min(id, id2);
}
}
}
}
// Detect bridge directions.
{
std::sort(bridge_anchors.begin(), bridge_anchors.end(), Algorithm::lower_by_src_and_boundary);
auto it_bridge_anchor = bridge_anchors.begin();
Lines lines;
Polygons anchor_areas;
for (uint32_t bridge_id = 0; bridge_id < uint32_t(bridges.size()); ++ bridge_id) {
Bridge &bridge = bridges[bridge_id];
// lines.clear();
anchor_areas.clear();
int32_t last_anchor_id = -1;
for (; it_bridge_anchor != bridge_anchors.end() && it_bridge_anchor->src == bridge_id; ++ it_bridge_anchor) {
if (last_anchor_id != int(it_bridge_anchor->boundary)) {
last_anchor_id = int(it_bridge_anchor->boundary);
append(anchor_areas, to_polygons(shells[last_anchor_id]));
}
// if (Points &polyline = it_bridge_anchor->path; polyline.size() >= 2) {
// reserve_more_power_of_2(lines, polyline.size() - 1);
// for (size_t i = 1; i < polyline.size(); ++ i)
// lines.push_back({ polyline[i - 1], polyline[1] });
// }
}
lines = to_lines(diff_pl(to_polylines(bridge.expolygon), expand(anchor_areas, float(SCALED_EPSILON))));
auto [bridging_dir, unsupported_dist] = detect_bridging_direction(lines, to_polygons(bridge.expolygon));
bridge.angle = M_PI + std::atan2(bridging_dir.y(), bridging_dir.x());
// #if 1
// coordf_t stroke_width = scale_(0.06);
// BoundingBox bbox = get_extents(initial);
// bbox.offset(scale_(1.));
// ::Slic3r::SVG
// svg(debug_out_path(("bridge"+std::to_string(bridges[idx_last].bridge_angle)+"_"+std::to_string(this->layer()->bottom_z())).c_str()),
// bbox);
// svg.draw(initial, "cyan");
// svg.draw(to_lines(lower_layer->lslices), "green", stroke_width);
// #endif
}
}
// Merge the groups with the same group id, produce surfaces by merging source overhangs with their newly expanded anchors.
Surfaces out;
{
Polygons acc;
Surface templ{ stBottomBridge, {} };
std::sort(bridge_expansions.begin(), bridge_expansions.end(), [](auto &l, auto &r) {
return l.src_id < r.src_id || (l.src_id == r.src_id && l.boundary_id < r.boundary_id);
});
for (auto it = bridge_expansions.begin(); it != bridge_expansions.end(); ) {
bridges[it->src_id].bridge_expansion_begin = it;
uint32_t src_id = it->src_id;
for (++ it; it != bridge_expansions.end() && it->src_id == src_id; ++ it) ;
}
for (uint32_t bridge_id = 0; bridge_id < uint32_t(bridges.size()); ++ bridge_id) {
acc.clear();
for (uint32_t bridge_id2 = bridge_id; bridge_id2 < uint32_t(bridges.size()); ++ bridge_id2)
if (group_id(bridge_id) == bridge_id) {
append(acc, to_polygons(std::move(bridges[bridge_id2].expolygon)));
auto it_bridge_expansion = bridges[bridge_id2].bridge_expansion_begin;
assert(it_bridge_expansion == bridge_expansions.end() || it_bridge_expansion->src_id == bridge_id2);
for (; it_bridge_expansion != bridge_expansions.end() && it_bridge_expansion->src_id == bridge_id2; ++ it_bridge_expansion)
append(acc, to_polygons(std::move(it_bridge_expansion->expolygon)));
}
//FIXME try to be smart and pick the best bridging angle for all?
templ.bridge_angle = bridges[bridge_id].angle;
// without safety offset, artifacts are generated (GH #2494)
for (ExPolygon &ex : union_safety_offset_ex(acc))
out.emplace_back(templ, std::move(ex));
}
}
// Clip the shells by the expanded bridges.
shells = diff_ex(shells, out);
return out;
}
// Extract bridging surfaces from "surfaces", expand them into "shells" using expansion_params.
// Trim "shells" by the expanded bridges.
static Surfaces expand_merge_surfaces(
Surfaces &surfaces,
SurfaceType surface_type,
ExPolygons &shells,
const Algorithm::RegionExpansionParameters &params,
const double bridge_angle = -1.)
{
double thickness;
ExPolygons src = fill_surfaces_extract_expolygons(surfaces, surface_type, thickness);
if (src.empty())
return {};
std::vector<ExPolygon> expanded = expand_merge_expolygons(std::move(src), shells, params);
// Trim the shells by the expanded expolygons.
shells = diff_ex(shells, expanded);
Surface templ{ surface_type, {} };
templ.bridge_angle = bridge_angle;
Surfaces out;
out.reserve(expanded.size());
for (auto &expoly : expanded)
out.emplace_back(templ, std::move(expoly));
return out;
}
void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered)
{
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-initial");
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
// Width of the perimeters.
float shell_width = 0;
if (int num_perimeters = this->region().config().perimeters; num_perimeters > 0) {
Flow external_perimeter_flow = this->flow(frExternalPerimeter);
Flow perimeter_flow = this->flow(frPerimeter);
shell_width += 0.5f * external_perimeter_flow.scaled_width() + external_perimeter_flow.scaled_spacing();
shell_width += perimeter_flow.scaled_spacing() * (num_perimeters - 1);
} else {
// TODO: Maybe there is better solution when printing with zero perimeters, but this works reasonably well, given the situation
shell_width = SCALED_EPSILON;
}
// Scaled expansions of the respective external surfaces.
float expansion_top = shell_width * sqrt(2.);
float expansion_bottom = expansion_top;
float expansion_bottom_bridge = expansion_top;
// Expand by waves of expansion_step size (expansion_step is scaled), but with no more steps than max_nr_expansion_steps.
static constexpr const float expansion_step = scaled<float>(0.1);
// Don't take more than max_nr_steps for small expansion_step.
static constexpr const size_t max_nr_expansion_steps = 5;
// Expand the top / bottom / bridge surfaces into the shell thickness solid infills.
double layer_thickness;
ExPolygons shells = union_ex(fill_surfaces_extract_expolygons(m_fill_surfaces.surfaces, stInternalSolid, layer_thickness));
SurfaceCollection bridges;
{
BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges. layer" << this->layer()->print_z;
const double custom_angle = this->region().config().bridge_angle.value;
const auto params = Algorithm::RegionExpansionParameters::build(expansion_bottom_bridge, expansion_step, max_nr_expansion_steps);
bridges.surfaces = custom_angle > 0 ?
expand_merge_surfaces(m_fill_surfaces.surfaces, stBottomBridge, shells, params, custom_angle) :
expand_bridges_detect_orientations(m_fill_surfaces.surfaces, shells, params);
BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges - done";
#if 0
{
static int iRun = 0;
bridges.export_to_svg(debug_out_path("bridges-after-grouping-%d.svg", iRun++), true);
}
#endif
}
Surfaces bottoms = expand_merge_surfaces(m_fill_surfaces.surfaces, stBottom, shells,
Algorithm::RegionExpansionParameters::build(expansion_bottom, expansion_step, max_nr_expansion_steps));
Surfaces tops = expand_merge_surfaces(m_fill_surfaces.surfaces, stTop, shells,
Algorithm::RegionExpansionParameters::build(expansion_top, expansion_step, max_nr_expansion_steps));
m_fill_surfaces.remove_types({ stBottomBridge, stBottom, stTop, stInternalSolid });
reserve_more(m_fill_surfaces.surfaces, shells.size() + bridges.size() + bottoms.size() + tops.size());
Surface solid_templ(stInternalSolid, {});
solid_templ.thickness = layer_thickness;
m_fill_surfaces.append(std::move(shells), solid_templ);
m_fill_surfaces.append(std::move(bridges.surfaces));
m_fill_surfaces.append(std::move(bottoms));
m_fill_surfaces.append(std::move(tops));
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-final");
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
}
#else
//#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 3.
//#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 1.5
#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0.
@ -146,10 +416,11 @@ void LayerRegion::make_perimeters(
void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered)
{
const bool has_infill = this->region().config().fill_density.value > 0.;
const float margin = float(scale_(EXTERNAL_INFILL_MARGIN));
// const float margin = scaled<float>(0.1); // float(scale_(EXTERNAL_INFILL_MARGIN));
const float margin = float(scale_(EXTERNAL_INFILL_MARGIN));
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-initial");
export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-initial");
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
// 1) Collect bottom and bridge surfaces, each of them grown by a fixed 3mm offset
@ -164,7 +435,6 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
Surfaces internal;
// Areas, where an infill of various types (top, bottom, bottom bride, sparse, void) could be placed.
Polygons fill_boundaries = to_polygons(this->fill_expolygons());
Polygons lower_layer_covered_tmp;
// Collect top surfaces and internal surfaces.
// Collect fill_boundaries: If we're slicing with no infill, we can't extend external surfaces over non-existent infill.
@ -174,33 +444,42 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
// Voids are sparse infills if infill rate is zero.
Polygons voids;
for (const Surface &surface : this->fill_surfaces()) {
if (surface.is_top()) {
// Collect the top surfaces, inflate them and trim them by the bottom surfaces.
// This gives the priority to bottom surfaces.
surfaces_append(top, offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface);
} else if (surface.surface_type == stBottom || (surface.surface_type == stBottomBridge && lower_layer == nullptr)) {
// Grown by 3mm.
surfaces_append(bottom, offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface);
} else if (surface.surface_type == stBottomBridge) {
if (! surface.empty())
assert(! surface.empty());
if (! surface.empty()) {
if (surface.is_top()) {
// Collect the top surfaces, inflate them and trim them by the bottom surfaces.
// This gives the priority to bottom surfaces.
surfaces_append(top, offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface);
} else if (surface.surface_type == stBottom || (surface.surface_type == stBottomBridge && lower_layer == nullptr)) {
// Grown by 3mm.
surfaces_append(bottom, offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface);
} else if (surface.surface_type == stBottomBridge) {
bridges.emplace_back(surface);
}
if (surface.is_internal()) {
assert(surface.surface_type == stInternal || surface.surface_type == stInternalSolid);
if (! has_infill && lower_layer != nullptr)
polygons_append(voids, surface.expolygon);
internal.emplace_back(std::move(surface));
} else {
assert(surface.is_internal());
assert(surface.surface_type == stInternal || surface.surface_type == stInternalSolid);
if (! has_infill && lower_layer != nullptr)
polygons_append(voids, surface.expolygon);
internal.emplace_back(std::move(surface));
}
}
}
if (! has_infill && lower_layer != nullptr && ! voids.empty()) {
if (! voids.empty()) {
// There are some voids (empty infill regions) on this layer. Usually one does not want to expand
// any infill into these voids, with the exception the expanded infills are supported by layers below
// with nonzero inill.
assert(! has_infill && lower_layer != nullptr);
// Remove voids from fill_boundaries, that are not supported by the layer below.
Polygons lower_layer_covered_tmp;
if (lower_layer_covered == nullptr) {
lower_layer_covered = &lower_layer_covered_tmp;
lower_layer_covered_tmp = to_polygons(lower_layer->lslices);
}
if (! lower_layer_covered->empty())
// Allow the top / bottom surfaces to expand into the voids of this layer if supported by the layer below.
voids = diff(voids, *lower_layer_covered);
fill_boundaries = diff(fill_boundaries, voids);
if (! voids.empty())
fill_boundaries = diff(fill_boundaries, voids);
}
}
@ -224,13 +503,12 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
{
static int iRun = 0;
SVG svg(debug_out_path("3_process_external_surfaces-fill_regions-%d.svg", iRun ++).c_str(), get_extents(fill_boundaries_ex));
SVG svg(debug_out_path("4_process_external_surfaces-fill_regions-%d.svg", iRun ++).c_str(), get_extents(fill_boundaries_ex));
svg.draw(fill_boundaries_ex);
svg.draw_outline(fill_boundaries_ex, "black", "blue", scale_(0.05));
svg.Close();
}
// export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-initial");
// export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-initial");
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
{
@ -253,7 +531,8 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
if (idx_island == -1) {
BOOST_LOG_TRIVIAL(trace) << "Bridge did not fall into the source region!";
} else {
// Found an island, to which this bridge region belongs. Trim it,
// Found an island, to which this bridge region belongs. Trim the expanded bridging region
// with its source region, so it does not overflow into a neighbor region.
polys = intersection(polys, fill_boundaries_ex[idx_island]);
}
bridge_bboxes.push_back(get_extents(polys));
@ -371,10 +650,10 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
Surfaces new_surfaces;
{
// Merge top and bottom in a single collection.
surfaces_append(top, std::move(bottom));
// Intersect the grown surfaces with the actual fill boundaries.
Polygons bottom_polygons = to_polygons(bottom);
// Merge top and bottom in a single collection.
surfaces_append(top, std::move(bottom));
for (size_t i = 0; i < top.size(); ++ i) {
Surface &s1 = top[i];
if (s1.empty())
@ -422,9 +701,10 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
m_fill_surfaces.surfaces = std::move(new_surfaces);
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-final");
export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-final");
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
}
#endif
void LayerRegion::prepare_fill_surfaces()
{

View File

@ -1309,7 +1309,7 @@ indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes conn
if (connector_attributes.style == CutConnectorStyle::Prism)
connector_mesh = its_make_cylinder(1.0, 1.0, (2 * PI / sectorCount));
else if (connector_attributes.type == CutConnectorType::Plug)
connector_mesh = its_make_cone(1.0, 1.0, (2 * PI / sectorCount));
connector_mesh = its_make_frustum(1.0, 1.0, (2 * PI / sectorCount));
else
connector_mesh = its_make_frustum_dowel(1.0, 1.0, sectorCount);
@ -1522,6 +1522,11 @@ void ModelObject::process_modifier_cut(ModelVolume* volume, const Transform3d& i
// to the modifier volume transformation to preserve their shape properly.
volume->set_transformation(Geometry::Transformation(volume_matrix));
if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) {
upper->add_volume(*volume);
return;
}
// Some logic for the negative volumes/connectors. Add only needed modifiers
auto bb = volume->mesh().transformed_bounding_box(inverse_cut_matrix * volume_matrix);
bool is_crossed_by_cut = bb.min[Z] <= 0 && bb.max[Z] >= 0;

View File

@ -433,10 +433,12 @@ static ClipperLib_Z::Paths clip_extrusion(const ClipperLib_Z::Path &subject, con
clipper.AddPath(subject, ClipperLib_Z::ptSubject, false);
clipper.AddPaths(clip, ClipperLib_Z::ptClip, true);
ClipperLib_Z::PolyTree clipped_polytree;
ClipperLib_Z::Paths clipped_paths;
clipper.Execute(clipType, clipped_polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
ClipperLib_Z::PolyTreeToPaths(clipped_polytree, clipped_paths);
{
ClipperLib_Z::PolyTree clipped_polytree;
clipper.Execute(clipType, clipped_polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
ClipperLib_Z::PolyTreeToPaths(std::move(clipped_polytree), clipped_paths);
}
// Clipped path could contain vertices from the clip with a Z coordinate equal to zero.
// For those vertices, we must assign value based on the subject.

View File

@ -185,75 +185,107 @@ namespace client
template<typename Iterator>
struct expr
{
expr() : type(TYPE_EMPTY) {}
explicit expr(bool b) : type(TYPE_BOOL) { data.b = b; }
explicit expr(bool b, const Iterator &it_begin, const Iterator &it_end) : type(TYPE_BOOL), it_range(it_begin, it_end) { data.b = b; }
explicit expr(int i) : type(TYPE_INT) { data.i = i; }
explicit expr(int i, const Iterator &it_begin, const Iterator &it_end) : type(TYPE_INT), it_range(it_begin, it_end) { data.i = i; }
explicit expr(double d) : type(TYPE_DOUBLE) { data.d = d; }
explicit expr(double d, const Iterator &it_begin, const Iterator &it_end) : type(TYPE_DOUBLE), it_range(it_begin, it_end) { data.d = d; }
explicit expr(const char *s) : type(TYPE_STRING) { data.s = new std::string(s); }
explicit expr(const std::string &s) : type(TYPE_STRING) { data.s = new std::string(s); }
expr() {}
explicit expr(bool b) : m_type(TYPE_BOOL) { m_data.b = b; }
explicit expr(bool b, const Iterator &it_begin, const Iterator &it_end) : m_type(TYPE_BOOL), it_range(it_begin, it_end) { m_data.b = b; }
explicit expr(int i) : m_type(TYPE_INT) { m_data.i = i; }
explicit expr(int i, const Iterator &it_begin, const Iterator &it_end) : m_type(TYPE_INT), it_range(it_begin, it_end) { m_data.i = i; }
explicit expr(double d) : m_type(TYPE_DOUBLE) { m_data.d = d; }
explicit expr(double d, const Iterator &it_begin, const Iterator &it_end) : m_type(TYPE_DOUBLE), it_range(it_begin, it_end) { m_data.d = d; }
explicit expr(const char *s) : m_type(TYPE_STRING) { m_data.s = new std::string(s); }
explicit expr(const std::string &s) : m_type(TYPE_STRING) { m_data.s = new std::string(s); }
explicit expr(const std::string &s, const Iterator &it_begin, const Iterator &it_end) :
type(TYPE_STRING), it_range(it_begin, it_end) { data.s = new std::string(s); }
expr(const expr &rhs) : type(rhs.type), it_range(rhs.it_range)
{ if (rhs.type == TYPE_STRING) data.s = new std::string(*rhs.data.s); else data.set(rhs.data); }
explicit expr(expr &&rhs) : type(rhs.type), it_range(rhs.it_range)
{ data.set(rhs.data); rhs.type = TYPE_EMPTY; }
explicit expr(expr &&rhs, const Iterator &it_begin, const Iterator &it_end) : type(rhs.type), it_range(it_begin, it_end)
{ data.set(rhs.data); rhs.type = TYPE_EMPTY; }
~expr() { this->reset(); }
expr &operator=(const expr &rhs)
m_type(TYPE_STRING), it_range(it_begin, it_end) { m_data.s = new std::string(s); }
expr(const expr &rhs) : m_type(rhs.type()), it_range(rhs.it_range)
{ if (rhs.type() == TYPE_STRING) m_data.s = new std::string(*rhs.m_data.s); else m_data.set(rhs.m_data); }
explicit expr(expr &&rhs) : expr(rhs, rhs.it_range.begin(), rhs.it_range.end()) {}
explicit expr(expr &&rhs, const Iterator &it_begin, const Iterator &it_end) : m_type(rhs.type()), it_range{ it_begin, it_end }
{
m_data.set(rhs.m_data);
rhs.m_type = TYPE_EMPTY;
}
expr &operator=(const expr &rhs)
{
this->type = rhs.type;
this->it_range = rhs.it_range;
if (rhs.type == TYPE_STRING)
this->data.s = new std::string(*rhs.data.s);
else
this->data.set(rhs.data);
return *this;
if (rhs.type() == TYPE_STRING) {
this->set_s(rhs.s());
} else {
m_type = rhs.type();
m_data.set(rhs.m_data);
}
this->it_range = rhs.it_range;
return *this;
}
expr &operator=(expr &&rhs)
{
type = rhs.type;
this->it_range = rhs.it_range;
data.set(rhs.data);
rhs.type = TYPE_EMPTY;
if (this != &rhs) {
this->reset();
m_type = rhs.type();
this->it_range = rhs.it_range;
m_data.set(rhs.m_data);
rhs.m_type = TYPE_EMPTY;
}
return *this;
}
void reset()
{
if (this->type == TYPE_STRING)
delete data.s;
this->type = TYPE_EMPTY;
if (this->type() == TYPE_STRING)
delete m_data.s;
m_type = TYPE_EMPTY;
}
bool& b() { return data.b; }
bool b() const { return data.b; }
void set_b(bool v) { this->reset(); this->data.b = v; this->type = TYPE_BOOL; }
int& i() { return data.i; }
int i() const { return data.i; }
void set_i(int v) { this->reset(); this->data.i = v; this->type = TYPE_INT; }
int as_i() const { return (this->type == TYPE_INT) ? this->i() : int(this->d()); }
int as_i_rounded() const { return (this->type == TYPE_INT) ? this->i() : int(std::round(this->d())); }
double& d() { return data.d; }
double d() const { return data.d; }
void set_d(double v) { this->reset(); this->data.d = v; this->type = TYPE_DOUBLE; }
double as_d() const { return (this->type == TYPE_DOUBLE) ? this->d() : double(this->i()); }
std::string& s() { return *data.s; }
const std::string& s() const { return *data.s; }
void set_s(const std::string &s) { this->reset(); this->data.s = new std::string(s); this->type = TYPE_STRING; }
void set_s(std::string &&s) { this->reset(); this->data.s = new std::string(std::move(s)); this->type = TYPE_STRING; }
enum Type {
TYPE_EMPTY = 0,
TYPE_BOOL,
TYPE_INT,
TYPE_DOUBLE,
TYPE_STRING,
};
Type type() const { return m_type; }
bool& b() { return m_data.b; }
bool b() const { return m_data.b; }
void set_b(bool v) { this->reset(); this->set_b_lite(v); }
void set_b_lite(bool v) { assert(this->type() != TYPE_STRING); Data tmp; tmp.b = v; m_data.set(tmp); m_type = TYPE_BOOL; }
int& i() { return m_data.i; }
int i() const { return m_data.i; }
void set_i(int v) { this->reset(); set_i_lite(v); }
void set_i_lite(int v) { assert(this->type() != TYPE_STRING); Data tmp; tmp.i = v; m_data.set(tmp); m_type = TYPE_INT; }
int as_i() const { return this->type() == TYPE_INT ? this->i() : int(this->d()); }
int as_i_rounded() const { return this->type() == TYPE_INT ? this->i() : int(std::round(this->d())); }
double& d() { return m_data.d; }
double d() const { return m_data.d; }
void set_d(double v) { this->reset(); this->set_d_lite(v); }
void set_d_lite(double v) { assert(this->type() != TYPE_STRING); Data tmp; tmp.d = v; m_data.set(tmp); m_type = TYPE_DOUBLE; }
double as_d() const { return this->type() == TYPE_DOUBLE ? this->d() : double(this->i()); }
std::string& s() { return *m_data.s; }
const std::string& s() const { return *m_data.s; }
void set_s(const std::string &s) {
if (this->type() == TYPE_STRING)
*m_data.s = s;
else
this->set_s_take_ownership(new std::string(s));
}
void set_s(std::string &&s) {
if (this->type() == TYPE_STRING)
*m_data.s = std::move(s);
else
this->set_s_take_ownership(new std::string(std::move(s)));
}
void set_s(const char *s) {
if (this->type() == TYPE_STRING)
*m_data.s = s;
else
this->set_s_take_ownership(new std::string(s));
}
std::string to_string() const
{
std::string out;
switch (type) {
case TYPE_BOOL: out = data.b ? "true" : "false"; break;
case TYPE_INT: out = std::to_string(data.i); break;
switch (this->type()) {
case TYPE_BOOL: out = this->b() ? "true" : "false"; break;
case TYPE_INT: out = std::to_string(this->i()); break;
case TYPE_DOUBLE:
#if 0
// The default converter produces trailing zeros after the decimal point.
@ -263,48 +295,24 @@ namespace client
// It seems to be doing what the old boost::to_string() did.
{
std::ostringstream ss;
ss << data.d;
ss << this->d();
out = ss.str();
}
#endif
break;
case TYPE_STRING: out = *data.s; break;
case TYPE_STRING: out = this->s(); break;
default: break;
}
return out;
}
union Data {
// Raw image of the other data members.
// The C++ compiler will consider a possible aliasing of char* with any other union member,
// therefore copying the raw data is safe.
char raw[8];
bool b;
int i;
double d;
std::string *s;
// Copy the largest member variable through char*, which will alias with all other union members by default.
void set(const Data &rhs) { memcpy(this->raw, rhs.raw, sizeof(rhs.raw)); }
} data;
enum Type {
TYPE_EMPTY = 0,
TYPE_BOOL,
TYPE_INT,
TYPE_DOUBLE,
TYPE_STRING,
};
Type type;
// Range of input iterators covering this expression.
// Used for throwing parse exceptions.
boost::iterator_range<Iterator> it_range;
expr unary_minus(const Iterator start_pos) const
{
switch (this->type) {
switch (this->type()) {
case TYPE_INT :
return expr<Iterator>(- this->i(), start_pos, this->it_range.end());
case TYPE_DOUBLE:
@ -319,7 +327,7 @@ namespace client
expr unary_integer(const Iterator start_pos) const
{
switch (this->type) {
switch (this->type()) {
case TYPE_INT:
return expr<Iterator>(this->i(), start_pos, this->it_range.end());
case TYPE_DOUBLE:
@ -334,7 +342,7 @@ namespace client
expr round(const Iterator start_pos) const
{
switch (this->type) {
switch (this->type()) {
case TYPE_INT:
return expr<Iterator>(this->i(), start_pos, this->it_range.end());
case TYPE_DOUBLE:
@ -349,7 +357,7 @@ namespace client
expr unary_not(const Iterator start_pos) const
{
switch (this->type) {
switch (this->type()) {
case TYPE_BOOL:
return expr<Iterator>(! this->b(), start_pos, this->it_range.end());
default:
@ -362,23 +370,20 @@ namespace client
expr &operator+=(const expr &rhs)
{
if (this->type == TYPE_STRING) {
if (this->type() == TYPE_STRING) {
// Convert the right hand side to string and append.
*this->data.s += rhs.to_string();
} else if (rhs.type == TYPE_STRING) {
*m_data.s += rhs.to_string();
} else if (rhs.type() == TYPE_STRING) {
// Conver the left hand side to string, append rhs.
this->data.s = new std::string(this->to_string() + rhs.s());
this->type = TYPE_STRING;
this->set_s(this->to_string() + rhs.s());
} else {
const char *err_msg = "Cannot add non-numeric types.";
this->throw_if_not_numeric(err_msg);
rhs.throw_if_not_numeric(err_msg);
if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) {
double d = this->as_d() + rhs.as_d();
this->data.d = d;
this->type = TYPE_DOUBLE;
} else
this->data.i += rhs.i();
if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE)
this->set_d_lite(this->as_d() + rhs.as_d());
else
m_data.i += rhs.i();
}
this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end());
return *this;
@ -389,12 +394,10 @@ namespace client
const char *err_msg = "Cannot subtract non-numeric types.";
this->throw_if_not_numeric(err_msg);
rhs.throw_if_not_numeric(err_msg);
if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) {
double d = this->as_d() - rhs.as_d();
this->data.d = d;
this->type = TYPE_DOUBLE;
} else
this->data.i -= rhs.i();
if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE)
this->set_d_lite(this->as_d() - rhs.as_d());
else
m_data.i -= rhs.i();
this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end());
return *this;
}
@ -404,12 +407,10 @@ namespace client
const char *err_msg = "Cannot multiply with non-numeric type.";
this->throw_if_not_numeric(err_msg);
rhs.throw_if_not_numeric(err_msg);
if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) {
double d = this->as_d() * rhs.as_d();
this->data.d = d;
this->type = TYPE_DOUBLE;
} else
this->data.i *= rhs.i();
if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE)
this->set_d_lite(this->as_d() * rhs.as_d());
else
m_data.i *= rhs.i();
this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end());
return *this;
}
@ -418,14 +419,12 @@ namespace client
{
this->throw_if_not_numeric("Cannot divide a non-numeric type.");
rhs.throw_if_not_numeric("Cannot divide with a non-numeric type.");
if ((rhs.type == TYPE_INT) ? (rhs.i() == 0) : (rhs.d() == 0.))
if (rhs.type() == TYPE_INT ? (rhs.i() == 0) : (rhs.d() == 0.))
rhs.throw_exception("Division by zero");
if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) {
double d = this->as_d() / rhs.as_d();
this->data.d = d;
this->type = TYPE_DOUBLE;
} else
this->data.i /= rhs.i();
if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE)
this->set_d_lite(this->as_d() / rhs.as_d());
else
m_data.i /= rhs.i();
this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end());
return *this;
}
@ -434,14 +433,12 @@ namespace client
{
this->throw_if_not_numeric("Cannot divide a non-numeric type.");
rhs.throw_if_not_numeric("Cannot divide with a non-numeric type.");
if ((rhs.type == TYPE_INT) ? (rhs.i() == 0) : (rhs.d() == 0.))
if (rhs.type() == TYPE_INT ? (rhs.i() == 0) : (rhs.d() == 0.))
rhs.throw_exception("Division by zero");
if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) {
double d = std::fmod(this->as_d(), rhs.as_d());
this->data.d = d;
this->type = TYPE_DOUBLE;
} else
this->data.i %= rhs.i();
if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE)
this->set_d_lite(std::fmod(this->as_d(), rhs.as_d()));
else
m_data.i %= rhs.i();
this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end());
return *this;
}
@ -453,14 +450,14 @@ namespace client
static void evaluate_boolean(expr &self, bool &out)
{
if (self.type != TYPE_BOOL)
if (self.type() != TYPE_BOOL)
self.throw_exception("Not a boolean expression");
out = self.b();
}
static void evaluate_boolean_to_string(expr &self, std::string &out)
{
if (self.type != TYPE_BOOL)
if (self.type() != TYPE_BOOL)
self.throw_exception("Not a boolean expression");
out = self.b() ? "true" : "false";
}
@ -469,31 +466,31 @@ namespace client
static void compare_op(expr &lhs, expr &rhs, char op, bool invert)
{
bool value = false;
if ((lhs.type == TYPE_INT || lhs.type == TYPE_DOUBLE) &&
(rhs.type == TYPE_INT || rhs.type == TYPE_DOUBLE)) {
if ((lhs.type() == TYPE_INT || lhs.type() == TYPE_DOUBLE) &&
(rhs.type() == TYPE_INT || rhs.type() == TYPE_DOUBLE)) {
// Both types are numeric.
switch (op) {
case '=':
value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ?
value = (lhs.type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE) ?
(std::abs(lhs.as_d() - rhs.as_d()) < 1e-8) : (lhs.i() == rhs.i());
break;
case '<':
value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ?
value = (lhs.type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE) ?
(lhs.as_d() < rhs.as_d()) : (lhs.i() < rhs.i());
break;
case '>':
default:
value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ?
value = (lhs.type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE) ?
(lhs.as_d() > rhs.as_d()) : (lhs.i() > rhs.i());
break;
}
} else if (lhs.type == TYPE_BOOL && rhs.type == TYPE_BOOL) {
} else if (lhs.type() == TYPE_BOOL && rhs.type() == TYPE_BOOL) {
// Both type are bool.
if (op != '=')
boost::throw_exception(qi::expectation_failure<Iterator>(
lhs.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot compare the types.")));
value = lhs.b() == rhs.b();
} else if (lhs.type == TYPE_STRING || rhs.type == TYPE_STRING) {
} else if (lhs.type() == TYPE_STRING || rhs.type() == TYPE_STRING) {
// One type is string, the other could be converted to string.
value = (op == '=') ? (lhs.to_string() == rhs.to_string()) :
(op == '<') ? (lhs.to_string() < rhs.to_string()) : (lhs.to_string() > rhs.to_string());
@ -502,8 +499,7 @@ namespace client
lhs.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot compare the types.")));
}
lhs.reset();
lhs.type = TYPE_BOOL;
lhs.data.b = invert ? ! value : value;
lhs.set_b_lite(invert ? ! value : value);
}
// Compare operators, store the result into lhs.
static void equal (expr &lhs, expr &rhs) { compare_op(lhs, rhs, '=', false); }
@ -528,15 +524,14 @@ namespace client
{
throw_if_not_numeric(param1);
throw_if_not_numeric(param2);
if (param1.type == TYPE_DOUBLE || param2.type == TYPE_DOUBLE) {
if (param1.type() == TYPE_DOUBLE || param2.type() == TYPE_DOUBLE) {
double d = 0.;
switch (fun) {
case FUNCTION_MIN: d = std::min(param1.as_d(), param2.as_d()); break;
case FUNCTION_MAX: d = std::max(param1.as_d(), param2.as_d()); break;
default: param1.throw_exception("Internal error: invalid function");
}
param1.data.d = d;
param1.type = TYPE_DOUBLE;
param1.set_d_lite(d);
} else {
int i = 0;
switch (fun) {
@ -544,8 +539,7 @@ namespace client
case FUNCTION_MAX: i = std::max(param1.as_i(), param2.as_i()); break;
default: param1.throw_exception("Internal error: invalid function");
}
param1.data.i = i;
param1.type = TYPE_INT;
param1.set_i_lite(i);
}
}
// Store the result into param1.
@ -557,13 +551,10 @@ namespace client
{
throw_if_not_numeric(param1);
throw_if_not_numeric(param2);
if (param1.type == TYPE_DOUBLE || param2.type == TYPE_DOUBLE) {
param1.data.d = std::uniform_real_distribution<>(param1.as_d(), param2.as_d())(rng);
param1.type = TYPE_DOUBLE;
} else {
param1.data.i = std::uniform_int_distribution<>(param1.as_i(), param2.as_i())(rng);
param1.type = TYPE_INT;
}
if (param1.type() == TYPE_DOUBLE || param2.type() == TYPE_DOUBLE)
param1.set_d_lite(std::uniform_real_distribution<>(param1.as_d(), param2.as_d())(rng));
else
param1.set_i_lite(std::uniform_int_distribution<>(param1.as_i(), param2.as_i())(rng));
}
// Store the result into param1.
@ -572,10 +563,10 @@ namespace client
static void digits(expr &param1, expr &param2, expr &param3)
{
throw_if_not_numeric(param1);
if (param2.type != TYPE_INT)
if (param2.type() != TYPE_INT)
param2.throw_exception("digits: second parameter must be integer");
bool has_decimals = param3.type != TYPE_EMPTY;
if (has_decimals && param3.type != TYPE_INT)
bool has_decimals = param3.type() != TYPE_EMPTY;
if (has_decimals && param3.type() != TYPE_INT)
param3.throw_exception("digits: third parameter must be integer");
char buf[256];
@ -593,7 +584,7 @@ namespace client
static void regex_op(expr &lhs, boost::iterator_range<Iterator> &rhs, char op)
{
const std::string *subject = nullptr;
if (lhs.type == TYPE_STRING) {
if (lhs.type() == TYPE_STRING) {
// One type is string, the other could be converted to string.
subject = &lhs.s();
} else {
@ -604,9 +595,7 @@ namespace client
bool result = SLIC3R_REGEX_NAMESPACE::regex_match(*subject, SLIC3R_REGEX_NAMESPACE::regex(pattern));
if (op == '!')
result = ! result;
lhs.reset();
lhs.type = TYPE_BOOL;
lhs.data.b = result;
lhs.set_b(result);
} catch (SLIC3R_REGEX_NAMESPACE::regex_error &ex) {
// Syntax error in the regular expression
boost::throw_exception(qi::expectation_failure<Iterator>(
@ -620,21 +609,20 @@ namespace client
static void logical_op(expr &lhs, expr &rhs, char op)
{
bool value = false;
if (lhs.type == TYPE_BOOL && rhs.type == TYPE_BOOL) {
if (lhs.type() == TYPE_BOOL && rhs.type() == TYPE_BOOL) {
value = (op == '|') ? (lhs.b() || rhs.b()) : (lhs.b() && rhs.b());
} else {
boost::throw_exception(qi::expectation_failure<Iterator>(
lhs.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot apply logical operation to non-boolean operators.")));
}
lhs.type = TYPE_BOOL;
lhs.data.b = value;
lhs.set_b_lite(value);
}
static void logical_or (expr &lhs, expr &rhs) { logical_op(lhs, rhs, '|'); }
static void logical_and(expr &lhs, expr &rhs) { logical_op(lhs, rhs, '&'); }
static void ternary_op(expr &lhs, expr &rhs1, expr &rhs2)
{
if (lhs.type != TYPE_BOOL)
if (lhs.type() != TYPE_BOOL)
lhs.throw_exception("Not a boolean expression");
if (lhs.b())
lhs = std::move(rhs1);
@ -658,9 +646,25 @@ namespace client
void throw_if_not_numeric(const char *message) const
{
if (this->type != TYPE_INT && this->type != TYPE_DOUBLE)
if (this->type() != TYPE_INT && this->type() != TYPE_DOUBLE)
this->throw_exception(message);
}
private:
// This object will take ownership of the parameter string object "s".
void set_s_take_ownership(std::string* s) { assert(this->type() != TYPE_STRING); Data tmp; tmp.s = s; m_data.set(tmp); m_type = TYPE_STRING; }
Type m_type = TYPE_EMPTY;
union Data {
bool b;
int i;
double d;
std::string *s;
// Copy the largest member variable through char*, which will alias with all other union members by default.
void set(const Data &rhs) { memcpy(this, &rhs, sizeof(rhs)); }
} m_data;
};
template<typename ITERATOR>
@ -668,7 +672,7 @@ namespace client
{
typedef expr<ITERATOR> Expr;
os << std::string(expression.it_range.begin(), expression.it_range.end()) << " - ";
switch (expression.type) {
switch (expression.type()) {
case Expr::TYPE_EMPTY: os << "empty"; break;
case Expr::TYPE_BOOL: os << "bool (" << expression.b() << ")"; break;
case Expr::TYPE_INT: os << "int (" << expression.i() << ")"; break;
@ -802,6 +806,7 @@ namespace client
case coInt: output.set_i(opt.opt->getInt()); break;
case coString: output.set_s(static_cast<const ConfigOptionString*>(opt.opt)->value); break;
case coPercent: output.set_d(opt.opt->getFloat()); break;
case coEnum:
case coPoint: output.set_s(opt.opt->serialize()); break;
case coBool: output.set_b(opt.opt->getBool()); break;
case coFloatOrPercent:
@ -869,6 +874,7 @@ namespace client
case coPercents: output.set_d(static_cast<const ConfigOptionPercents*>(opt.opt)->values[idx]); break;
case coPoints: output.set_s(to_string(static_cast<const ConfigOptionPoints *>(opt.opt)->values[idx])); break;
case coBools: output.set_b(static_cast<const ConfigOptionBools *>(opt.opt)->values[idx] != 0); break;
//case coEnums: output.set_s(opt.opt->vserialize()[idx]); break;
default:
ctx->throw_exception("Unknown vector variable type", opt.it_range);
}
@ -912,7 +918,7 @@ namespace client
template <typename Iterator>
static void evaluate_index(expr<Iterator> &expr_index, int &output)
{
if (expr_index.type != expr<Iterator>::TYPE_INT)
if (expr_index.type() != expr<Iterator>::TYPE_INT)
expr_index.throw_exception("Non-integer index is not allowed to address a vector variable.");
output = expr_index.i();
}

View File

@ -5,6 +5,7 @@
#include <map>
#include <random>
#include <string>
#include <string_view>
#include <vector>
#include "PrintConfig.hpp"
@ -38,6 +39,8 @@ public:
// Add new ConfigOption values to m_config.
void set(const std::string &key, const std::string &value) { this->set(key, new ConfigOptionString(value)); }
void set(const std::string &key, std::string_view value) { this->set(key, new ConfigOptionString(std::string(value))); }
void set(const std::string &key, const char *value) { this->set(key, new ConfigOptionString(value)); }
void set(const std::string &key, int value) { this->set(key, new ConfigOptionInt(value)); }
void set(const std::string &key, unsigned int value) { this->set(key, int(value)); }
void set(const std::string &key, bool value) { this->set(key, new ConfigOptionBool(value)); }

View File

@ -84,18 +84,28 @@ Points collect_duplicates(Points pts /* Copy */)
return duplicits;
}
template<bool IncludeBoundary>
BoundingBox get_extents(const Points &pts)
{
return BoundingBox(pts);
BoundingBox out;
BoundingBox::construct<IncludeBoundary>(out, pts.begin(), pts.end());
return out;
}
template BoundingBox get_extents<false>(const Points &pts);
template BoundingBox get_extents<true>(const Points &pts);
// if IncludeBoundary, then a bounding box is defined even for a single point.
// otherwise a bounding box is only defined if it has a positive area.
template<bool IncludeBoundary>
BoundingBox get_extents(const std::vector<Points> &pts)
{
BoundingBox bbox;
for (const Points &p : pts)
bbox.merge(get_extents(p));
bbox.merge(get_extents<IncludeBoundary>(p));
return bbox;
}
template BoundingBox get_extents<false>(const std::vector<Points> &pts);
template BoundingBox get_extents<true>(const std::vector<Points> &pts);
BoundingBoxf get_extents(const std::vector<Vec2d> &pts)
{

View File

@ -237,8 +237,20 @@ inline Point lerp(const Point &a, const Point &b, double t)
return ((1. - t) * a.cast<double>() + t * b.cast<double>()).cast<coord_t>();
}
// if IncludeBoundary, then a bounding box is defined even for a single point.
// otherwise a bounding box is only defined if it has a positive area.
template<bool IncludeBoundary = false>
BoundingBox get_extents(const Points &pts);
extern template BoundingBox get_extents<false>(const Points &pts);
extern template BoundingBox get_extents<true>(const Points &pts);
// if IncludeBoundary, then a bounding box is defined even for a single point.
// otherwise a bounding box is only defined if it has a positive area.
template<bool IncludeBoundary = false>
BoundingBox get_extents(const std::vector<Points> &pts);
extern template BoundingBox get_extents<false>(const std::vector<Points> &pts);
extern template BoundingBox get_extents<true>(const std::vector<Points> &pts);
BoundingBoxf get_extents(const std::vector<Vec2d> &pts);
int nearest_point_index(const Points &points, const Point &pt);

View File

@ -292,6 +292,19 @@ void ThickPolyline::clip_end(double distance)
assert(this->width.size() == (this->points.size() - 1) * 2);
}
void ThickPolyline::start_at_index(int index)
{
assert(index >= 0 && index < this->points.size());
assert(this->points.front() == this->points.back() && this->width.front() == this->width.back());
if (index != 0 && index != (this->points.size() - 1) && this->points.front() == this->points.back() && this->width.front() == this->width.back()) {
this->points.pop_back();
assert(this->points.size() * 2 == this->width.size());
std::rotate(this->points.begin(), this->points.begin() + index, this->points.end());
std::rotate(this->width.begin(), this->width.begin() + 2 * index, this->width.end());
this->points.emplace_back(this->points.front());
}
}
double Polyline3::length() const
{
double l = 0;

View File

@ -153,6 +153,25 @@ inline void polylines_append(Polylines &dst, Polylines &&src)
}
}
// Merge polylines at their respective end points.
// dst_first: the merge point is at dst.begin() or dst.end()?
// src_first: the merge point is at src.begin() or src.end()?
// The orientation of the resulting polyline is unknown, the output polyline may start
// either with src piece or dst piece.
template<typename PointType>
inline void polylines_merge(std::vector<PointType> &dst, bool dst_first, std::vector<PointType> &&src, bool src_first)
{
if (dst_first) {
if (src_first)
std::reverse(dst.begin(), dst.end());
else
std::swap(dst, src);
} else if (! src_first)
std::reverse(src.begin(), src.end());
// Merge src into dst.
append(dst, std::move(src));
}
const Point& leftmost_point(const Polylines &polylines);
bool remove_degenerate(Polylines &polylines);
@ -166,6 +185,7 @@ struct ThickPolyline {
const Point& first_point() const { return this->points.front(); }
const Point& last_point() const { return this->points.back(); }
size_t size() const { return this->points.size(); }
bool is_valid() const { return this->points.size() >= 2; }
double length() const { return Slic3r::length(this->points); }
@ -179,6 +199,11 @@ struct ThickPolyline {
void clip_end(double distance);
// Make this closed ThickPolyline starting in the specified index.
// Be aware that this method can be applicable just for closed ThickPolyline.
// On open ThickPolyline make no effect.
void start_at_index(int index);
Points points;
std::vector<coordf_t> width;
std::pair<bool,bool> endpoints { false, false };

View File

@ -431,7 +431,7 @@ void Preset::set_visible_from_appconfig(const AppConfig &app_config)
static std::vector<std::string> s_Preset_print_options {
"layer_height", "first_layer_height", "perimeters", "spiral_vase", "slice_closing_radius", "slicing_mode",
"top_solid_layers", "top_solid_min_thickness", "bottom_solid_layers", "bottom_solid_min_thickness",
"extra_perimeters", "extra_perimeters_on_overhangs", "ensure_vertical_shell_thickness", "avoid_crossing_curled_overhangs", "avoid_crossing_perimeters", "thin_walls", "overhangs",
"extra_perimeters", "extra_perimeters_on_overhangs", "avoid_crossing_curled_overhangs", "avoid_crossing_perimeters", "thin_walls", "overhangs",
"seam_position","staggered_inner_seams", "external_perimeters_first", "fill_density", "fill_pattern", "top_fill_pattern", "bottom_fill_pattern",
"infill_every_layers", "infill_only_where_needed", "solid_infill_every_layers", "fill_angle", "bridge_angle",
"solid_infill_below_area", "only_retract_when_crossing_perimeters", "infill_first",
@ -440,7 +440,7 @@ static std::vector<std::string> s_Preset_print_options {
"fuzzy_skin", "fuzzy_skin_thickness", "fuzzy_skin_point_dist",
"max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative",
"perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "infill_speed", "solid_infill_speed",
"enable_dynamic_overhang_speeds", "dynamic_overhang_speeds", "overhang_overlap_levels",
"enable_dynamic_overhang_speeds", "overhang_speed_0", "overhang_speed_1", "overhang_speed_2", "overhang_speed_3",
"top_solid_infill_speed", "support_material_speed", "support_material_xy_spacing", "support_material_interface_speed",
"bridge_speed", "gap_fill_speed", "gap_fill_enabled", "travel_speed", "travel_speed_z", "first_layer_speed", "first_layer_speed_over_raft", "perimeter_acceleration", "infill_acceleration",
"external_perimeter_acceleration", "top_solid_infill_acceleration", "solid_infill_acceleration",
@ -473,7 +473,8 @@ static std::vector<std::string> s_Preset_filament_options {
"filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", "filament_minimal_purge_on_wipe_tower",
"temperature", "idle_temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed",
"max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "full_fan_speed_layer", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed",
"start_filament_gcode", "end_filament_gcode",
"start_filament_gcode", "end_filament_gcode", "enable_dynamic_fan_speeds",
"overhang_fan_speed_0", "overhang_fan_speed_1", "overhang_fan_speed_2", "overhang_fan_speed_3",
// Retract overrides
"filament_retract_length", "filament_retract_lift", "filament_retract_lift_above", "filament_retract_lift_below", "filament_retract_speed", "filament_deretract_speed", "filament_retract_restart_extra", "filament_retract_before_travel",
"filament_retract_layer_change", "filament_wipe", "filament_retract_before_wipe",
@ -785,7 +786,7 @@ std::pair<Preset*, bool> PresetCollection::load_external_preset(
{
// Load the preset over a default preset, so that the missing fields are filled in from the default preset.
DynamicPrintConfig cfg(this->default_preset_for(combined_config).config);
t_config_option_keys keys = std::move(cfg.keys());
t_config_option_keys keys = cfg.keys();
cfg.apply_only(combined_config, keys, true);
std::string &inherits = Preset::inherits(cfg);
if (select == LoadAndSelect::Never) {
@ -879,7 +880,7 @@ std::pair<Preset*, bool> PresetCollection::load_external_preset(
// Insert a new profile.
Preset &preset = this->load_preset(path, new_name, std::move(cfg), select == LoadAndSelect::Always);
preset.is_external = true;
if (&this->get_selected_preset() == &preset)
if (this->m_idx_selected != size_t(-1) && &this->get_selected_preset() == &preset)
this->get_edited_preset().is_external = true;
return std::make_pair(&preset, false);

View File

@ -66,6 +66,11 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
"between_objects_gcode",
"bridge_acceleration",
"bridge_fan_speed",
"enable_dynamic_fan_speeds",
"overhang_fan_speed_0",
"overhang_fan_speed_1",
"overhang_fan_speed_2",
"overhang_fan_speed_3",
"colorprint_heights",
"cooling",
"default_acceleration",
@ -514,7 +519,7 @@ std::string Print::validate(std::string* warning) const
const PrintObject &print_object = *m_objects[print_object_idx];
//FIXME It is quite expensive to generate object layers just to get the print height!
if (auto layers = generate_object_layers(print_object.slicing_parameters(), layer_height_profile(print_object_idx));
! layers.empty() && layers.back() > this->config().max_print_height) {
! layers.empty() && layers.back() > this->config().max_print_height + EPSILON) {
return L("The print is taller than the maximum allowed height. You might want to reduce the size of your model"
" or change current print settings and retry.");
}

View File

@ -535,35 +535,96 @@ void PrintConfigDef::init_fff_params()
def->label = L("Enable dynamic overhang speeds");
def->category = L("Speed");
def->tooltip = L("This setting enables dynamic speed control on overhangs.");
def->mode = comAdvanced;
def->mode = comExpert;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("overhang_overlap_levels", coPercents);
def->full_label = L("Overhang overlap levels");
def->category = L("Speed");
def->tooltip = L("Controls overhang levels, expressed as a percentage of overlap of the extrusion with the previous layer - "
"100% represents full overlap - no overhang is present, while 0% represents full overhang (floating extrusion). "
"Each overhang level then corresponds with the overhang speed below. Speeds for overhang levels in between are "
"calculated via linear interpolation."
"If you set multiple different speeds for the same overhang level, only the largest speed is used. "
);
def->sidetext = L("%");
def->min = 0;
def->max = 100;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionPercents({60, 40, 20, 0}));
auto overhang_speed_setting_description = L("Overhang size is expressed as a percentage of overlap of the extrusion with the previous layer: "
"100% would be full overlap (no overhang), while 0% represents full overhang (floating extrusion, bridge). "
"Speeds for overhang sizes in between are calculated via linear interpolation. "
"If set as percentage, the speed is calculated over the external perimeter speed.");
def = this->add("dynamic_overhang_speeds", coFloatsOrPercents);
def->full_label = L("Dynamic speed on overhangs");
def = this->add("overhang_speed_0", coFloatOrPercent);
def->label = L("speed for 0% overlap (bridge)");
def->category = L("Speed");
def->tooltip = L("This setting controls the speed on the overhang with the overlap value set above. "
"The speed of the extrusion is calculated as a linear interpolation of the speeds for higher and lower overlap. "
"If set as percentage, the speed is calculated over the external perimeter speed."
);
def->tooltip = overhang_speed_setting_description;
def->sidetext = L("mm/s or %");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloatsOrPercents({{25, false}, {20, false}, {15, false}, {15, false}}));
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloatOrPercent(15, false));
def = this->add("overhang_speed_1", coFloatOrPercent);
def->label = L("speed for 25% overlap");
def->category = L("Speed");
def->tooltip = overhang_speed_setting_description;
def->sidetext = L("mm/s or %");
def->min = 0;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloatOrPercent(15, false));
def = this->add("overhang_speed_2", coFloatOrPercent);
def->label = L("speed for 50% overlap");
def->category = L("Speed");
def->tooltip = overhang_speed_setting_description;
def->sidetext = L("mm/s or %");
def->min = 0;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloatOrPercent(20, false));
def = this->add("overhang_speed_3", coFloatOrPercent);
def->label = L("speed for 75% overlap");
def->category = L("Speed");
def->tooltip = overhang_speed_setting_description;
def->sidetext = L("mm/s or %");
def->min = 0;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloatOrPercent(25, false));
def = this->add("enable_dynamic_fan_speeds", coBools);
def->label = L("Enable dynamic fan speeds");
def->tooltip = L("This setting enables dynamic fan speed control on overhangs.");
def->mode = comExpert;
def->set_default_value(new ConfigOptionBools{false});
auto fan_speed_setting_description = L(
"Overhang size is expressed as a percentage of overlap of the extrusion with the previous layer: "
"100% would be full overlap (no overhang), while 0% represents full overhang (floating extrusion, bridge). "
"Fan speeds for overhang sizes in between are calculated via linear interpolation. ");
def = this->add("overhang_fan_speed_0", coInts);
def->label = L("speed for 0% overlap (bridge)");
def->tooltip = fan_speed_setting_description;
def->sidetext = L("%");
def->min = 0;
def->max = 100;
def->mode = comExpert;
def->set_default_value(new ConfigOptionInts{0});
def = this->add("overhang_fan_speed_1", coInts);
def->label = L("speed for 25% overlap");
def->tooltip = fan_speed_setting_description;
def->sidetext = L("%");
def->min = 0;
def->max = 100;
def->mode = comExpert;
def->set_default_value(new ConfigOptionInts{0});
def = this->add("overhang_fan_speed_2", coInts);
def->label = L("speed for 50% overlap");
def->tooltip = fan_speed_setting_description;
def->sidetext = L("%");
def->min = 0;
def->max = 100;
def->mode = comExpert;
def->set_default_value(new ConfigOptionInts{0});
def = this->add("overhang_fan_speed_3", coInts);
def->label = L("speed for 75% overlap");
def->tooltip = fan_speed_setting_description;
def->sidetext = L("%");
def->min = 0;
def->max = 100;
def->mode = comExpert;
def->set_default_value(new ConfigOptionInts{0});
def = this->add("brim_width", coFloat);
def->label = L("Brim width");
@ -745,14 +806,6 @@ void PrintConfigDef::init_fff_params()
def->mode = comExpert;
def->set_default_value(new ConfigOptionStrings { "; Filament-specific end gcode \n;END gcode for filament\n" });
def = this->add("ensure_vertical_shell_thickness", coBool);
def->label = L("Ensure vertical shell thickness");
def->category = L("Layers and Perimeters");
def->tooltip = L("Add solid infill near sloping surfaces to guarantee the vertical shell thickness "
"(top+bottom solid layers).");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false));
auto def_top_fill_pattern = def = this->add("top_fill_pattern", coEnum);
def->label = L("Top fill pattern");
def->category = L("Infill");
@ -3270,9 +3323,6 @@ void PrintConfigDef::init_sla_support_params(const std::string &prefix)
{
ConfigOptionDef* def;
constexpr const char * pretext_unavailable = L("Unavailable for this method.\n");
std::string pretext;
def = this->add(prefix + "support_head_front_diameter", coFloat);
def->label = L("Pinhead front diameter");
def->category = L("Supports");
@ -3322,13 +3372,9 @@ void PrintConfigDef::init_sla_support_params(const std::string &prefix)
def->mode = comExpert;
def->set_default_value(new ConfigOptionPercent(50));
pretext = "";
if (prefix == "branching")
pretext = pretext_unavailable;
def = this->add(prefix + "support_max_bridges_on_pillar", coInt);
def->label = L("Max bridges on a pillar");
def->tooltip = pretext + L(
def->tooltip = L(
"Maximum number of bridges that can be placed on a pillar. Bridges "
"hold support point pinheads and connect to pillars as small branches.");
def->min = 0;
@ -3336,14 +3382,10 @@ void PrintConfigDef::init_sla_support_params(const std::string &prefix)
def->mode = comExpert;
def->set_default_value(new ConfigOptionInt(prefix == "branching" ? 2 : 3));
pretext = "";
if (prefix.empty())
pretext = pretext_unavailable;
def = this->add(prefix + "support_max_weight_on_model", coFloat);
def->label = L("Max weight on model");
def->category = L("Supports");
def->tooltip = pretext + L(
def->tooltip = L(
"Maximum weight of sub-trees that terminate on the model instead of the print bed. The weight is the sum of the lenghts of all "
"branches emanating from the endpoint.");
def->sidetext = L("mm");
@ -3351,13 +3393,9 @@ void PrintConfigDef::init_sla_support_params(const std::string &prefix)
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(10.));
pretext = "";
if (prefix == "branching")
pretext = pretext_unavailable;
def = this->add(prefix + "support_pillar_connection_mode", coEnum);
def->label = L("Pillar connection mode");
def->tooltip = pretext + L("Controls the bridge type between two neighboring pillars."
def->tooltip = L("Controls the bridge type between two neighboring pillars."
" Can be zig-zag, cross (double zig-zag) or dynamic which"
" will automatically switch between the first two depending"
" on the distance of the two pillars.");
@ -3378,11 +3416,7 @@ void PrintConfigDef::init_sla_support_params(const std::string &prefix)
def->label = L("Pillar widening factor");
def->category = L("Supports");
pretext = "";
if (prefix.empty())
pretext = pretext_unavailable;
def->tooltip = pretext +
def->tooltip =
L("Merging bridges or pillars into another pillars can "
"increase the radius. Zero means no increase, one means "
"full increase. The exact amount of increase is unspecified and can "
@ -3449,14 +3483,10 @@ void PrintConfigDef::init_sla_support_params(const std::string &prefix)
def->set_default_value(new ConfigOptionFloat(default_val));
pretext = "";
if (prefix == "branching")
pretext = pretext_unavailable;
def = this->add(prefix + "support_max_pillar_link_distance", coFloat);
def->label = L("Max pillar linking distance");
def->category = L("Supports");
def->tooltip = pretext + L("The max distance of two pillars to get linked with each other."
def->tooltip = L("The max distance of two pillars to get linked with each other."
" A zero value will prohibit pillar cascading.");
def->sidetext = L("mm");
def->min = 0; // 0 means no linking
@ -4059,6 +4089,8 @@ static std::set<std::string> PrintConfigDef_ignore = {
"fuzzy_skin_perimeter_mode", "fuzzy_skin_shape",
// Introduced in PrusaSlicer 2.3.0-alpha2, later replaced by automatic calculation based on extrusion width.
"wall_add_middle_threshold", "wall_split_middle_threshold",
// Replaced by new concentric ensuring in 2.6.0-alpha5
"ensure_vertical_shell_thickness",
};
void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value)
@ -4790,6 +4822,15 @@ Points get_bed_shape(const DynamicPrintConfig &config)
return to_points(bed_shape_opt->values);
}
void get_bed_shape(const DynamicPrintConfig &cfg, arrangement::ArrangeBed &out)
{
if (is_XL_printer(cfg)) {
out = arrangement::SegmentedRectangleBed{get_extents(get_bed_shape(cfg)), 4, 4};
} else {
out = arrangement::to_arrange_bed(get_bed_shape(cfg));
}
}
Points get_bed_shape(const PrintConfig &cfg)
{
return to_points(cfg.bed_shape.values);
@ -4814,6 +4855,20 @@ std::string get_sla_suptree_prefix(const DynamicPrintConfig &config)
return slatree;
}
bool is_XL_printer(const DynamicPrintConfig &cfg)
{
static constexpr const char *ALIGN_ONLY_FOR = "XL";
bool ret = false;
auto *printer_model = cfg.opt<ConfigOptionString>("printer_model");
if (printer_model)
ret = boost::algorithm::contains(printer_model->value, ALIGN_ONLY_FOR);
return ret;
}
} // namespace Slic3r
#include <cereal/types/polymorphic.hpp>

View File

@ -19,6 +19,7 @@
#include "libslic3r.h"
#include "Config.hpp"
#include "SLA/SupportTreeStrategies.hpp"
#include "libslic3r/Arrange.hpp"
#include <boost/preprocessor/facilities/empty.hpp>
#include <boost/preprocessor/punctuation/comma_if.hpp>
@ -60,6 +61,7 @@ enum InfillPattern : int {
ipRectilinear, ipMonotonic, ipMonotonicLines, ipAlignedRectilinear, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb,
ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipSupportCubic, ipSupportBase,
ipLightning,
ipEnsuring,
ipCount,
};
@ -162,6 +164,7 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeThumbnailsFormat)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PerimeterGeneratorType)
#undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS
// Defines each and every confiuration option of Slic3r, including the properties of the GUI dialogs.
@ -567,14 +570,15 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionFloat, bottom_solid_min_thickness))
((ConfigOptionFloat, bridge_flow_ratio))
((ConfigOptionFloat, bridge_speed))
((ConfigOptionBool, ensure_vertical_shell_thickness))
((ConfigOptionEnum<InfillPattern>, top_fill_pattern))
((ConfigOptionEnum<InfillPattern>, bottom_fill_pattern))
((ConfigOptionFloatOrPercent, external_perimeter_extrusion_width))
((ConfigOptionFloatOrPercent, external_perimeter_speed))
((ConfigOptionBool, enable_dynamic_overhang_speeds))
((ConfigOptionPercents, overhang_overlap_levels))
((ConfigOptionFloatsOrPercents, dynamic_overhang_speeds))
((ConfigOptionFloatOrPercent, overhang_speed_0))
((ConfigOptionFloatOrPercent, overhang_speed_1))
((ConfigOptionFloatOrPercent, overhang_speed_2))
((ConfigOptionFloatOrPercent, overhang_speed_3))
((ConfigOptionBool, external_perimeters_first))
((ConfigOptionBool, extra_perimeters))
((ConfigOptionBool, extra_perimeters_on_overhangs))
@ -747,6 +751,11 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
((ConfigOptionInts, bed_temperature))
((ConfigOptionFloat, bridge_acceleration))
((ConfigOptionInts, bridge_fan_speed))
((ConfigOptionBools, enable_dynamic_fan_speeds))
((ConfigOptionInts, overhang_fan_speed_0))
((ConfigOptionInts, overhang_fan_speed_1))
((ConfigOptionInts, overhang_fan_speed_2))
((ConfigOptionInts, overhang_fan_speed_3))
((ConfigOptionBool, complete_objects))
((ConfigOptionFloats, colorprint_heights))
((ConfigOptionBools, cooling))
@ -1177,10 +1186,14 @@ private:
static PrintAndCLIConfigDef s_def;
};
bool is_XL_printer(const DynamicPrintConfig &cfg);
Points get_bed_shape(const DynamicPrintConfig &cfg);
Points get_bed_shape(const PrintConfig &cfg);
Points get_bed_shape(const SLAPrinterConfig &cfg);
void get_bed_shape(const DynamicPrintConfig &cfg, arrangement::ArrangeBed &out);
std::string get_sla_suptree_prefix(const DynamicPrintConfig &config);
// ModelConfig is a wrapper around DynamicPrintConfig with an addition of a timestamp.

File diff suppressed because it is too large Load Diff

View File

@ -141,6 +141,7 @@ std::pair<bool, long> create_ground_pillar(
if (head_id >= 0) builder.head(head_id).bridge_id = br.id;
endp = diffbr->endp;
radius = diffbr->end_r;
end_radius = diffbr->end_r;
builder.add_junction(endp, radius);
non_head = true;
dir = diffbr->get_dir();

View File

@ -31,7 +31,7 @@ inline coordf_t min_layer_height_from_nozzle(const PrintConfig &print_config, in
return (min_layer_height == 0.) ? MIN_LAYER_HEIGHT_DEFAULT : std::max(MIN_LAYER_HEIGHT, min_layer_height);
}
// Maximum layer height for the variable layer height algorithm, 3/4 of a nozzle dimaeter by default,
// Maximum layer height for the variable layer height algorithm, 3/4 of a nozzle diameter by default,
// it should not be smaller than the minimum layer height.
inline coordf_t max_layer_height_from_nozzle(const PrintConfig &print_config, int idx_nozzle)
{
@ -48,7 +48,7 @@ coordf_t Slicing::min_layer_height_from_nozzle(const DynamicPrintConfig &print_c
return (min_layer_height == 0.) ? MIN_LAYER_HEIGHT_DEFAULT : std::max(MIN_LAYER_HEIGHT, min_layer_height);
}
// Maximum layer height for the variable layer height algorithm, 3/4 of a nozzle dimaeter by default,
// Maximum layer height for the variable layer height algorithm, 3/4 of a nozzle diameter by default,
// it should not be smaller than the minimum layer height.
coordf_t Slicing::max_layer_height_from_nozzle(const DynamicPrintConfig &print_config, int idx_nozzle)
{

View File

@ -376,8 +376,6 @@ SupportParameters::SupportParameters(const PrintObject &object)
}
this->base_angle = Geometry::deg2rad(float(object_config.support_material_angle.value));
this->interface_angle = Geometry::deg2rad(float(object_config.support_material_angle.value + 90.));
double interface_spacing = object_config.support_material_interface_spacing.value + this->support_material_interface_flow.spacing();
this->interface_density = std::min(1., this->support_material_interface_flow.spacing() / interface_spacing);
double raft_interface_spacing = object_config.support_material_interface_spacing.value + this->raft_interface_flow.spacing();
@ -401,6 +399,39 @@ SupportParameters::SupportParameters(const PrintObject &object)
object_config.support_material_interface_pattern == smipConcentric ?
ipConcentric :
(this->interface_density > 0.95 ? ipRectilinear : ipSupportBase);
this->base_angle = Geometry::deg2rad(float(object_config.support_material_angle.value));
this->interface_angle = Geometry::deg2rad(float(object_config.support_material_angle.value + 90.));
this->raft_angle_1st_layer = 0.f;
this->raft_angle_base = 0.f;
this->raft_angle_interface = 0.f;
if (slicing_params.base_raft_layers > 1) {
assert(slicing_params.raft_layers() >= 4);
// There are all raft layer types (1st layer, base, interface & contact layers) available.
this->raft_angle_1st_layer = this->interface_angle;
this->raft_angle_base = this->base_angle;
this->raft_angle_interface = this->interface_angle;
if ((slicing_params.interface_raft_layers & 1) == 0)
// Allign the 1st raft interface layer so that the object 1st layer is hatched perpendicularly to the raft contact interface.
this->raft_angle_interface += float(0.5 * M_PI);
} else if (slicing_params.base_raft_layers == 1 || slicing_params.interface_raft_layers > 1) {
assert(slicing_params.raft_layers() == 2 || slicing_params.raft_layers() == 3);
// 1st layer, interface & contact layers available.
this->raft_angle_1st_layer = this->base_angle;
this->raft_angle_interface = this->interface_angle + 0.5 * M_PI;
} else if (slicing_params.interface_raft_layers == 1) {
// Only the contact raft layer is non-empty, which will be printed as the 1st layer.
assert(slicing_params.base_raft_layers == 0);
assert(slicing_params.interface_raft_layers == 1);
assert(slicing_params.raft_layers() == 1);
this->raft_angle_1st_layer = float(0.5 * M_PI);
this->raft_angle_interface = this->raft_angle_1st_layer;
} else {
// No raft.
assert(slicing_params.base_raft_layers == 0);
assert(slicing_params.interface_raft_layers == 0);
assert(slicing_params.raft_layers() == 0);
}
}
PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object, const SlicingParameters &slicing_params) :
@ -4207,38 +4238,12 @@ void generate_support_toolpaths(
// const coordf_t link_max_length_factor = 3.;
const coordf_t link_max_length_factor = 0.;
float raft_angle_1st_layer = 0.f;
float raft_angle_base = 0.f;
float raft_angle_interface = 0.f;
if (slicing_params.base_raft_layers > 1) {
// There are all raft layer types (1st layer, base, interface & contact layers) available.
raft_angle_1st_layer = support_params.interface_angle;
raft_angle_base = support_params.base_angle;
raft_angle_interface = support_params.interface_angle;
} else if (slicing_params.base_raft_layers == 1 || slicing_params.interface_raft_layers > 1) {
// 1st layer, interface & contact layers available.
raft_angle_1st_layer = support_params.base_angle;
if (config.support_material || config.support_material_enforce_layers > 0)
// Print 1st layer at 45 degrees from both the interface and base angles as both can land on the 1st layer.
raft_angle_1st_layer += 0.7854f;
raft_angle_interface = support_params.interface_angle;
} else if (slicing_params.interface_raft_layers == 1) {
// Only the contact raft layer is non-empty, which will be printed as the 1st layer.
assert(slicing_params.base_raft_layers == 0);
assert(slicing_params.interface_raft_layers == 1);
assert(slicing_params.raft_layers() == 1 && raft_layers.size() == 0);
} else {
// No raft.
assert(slicing_params.base_raft_layers == 0);
assert(slicing_params.interface_raft_layers == 0);
assert(slicing_params.raft_layers() == 0 && raft_layers.size() == 0);
}
// Insert the raft base layers.
auto n_raft_layers = std::min<size_t>(support_layers.size(), std::max(0, int(slicing_params.raft_layers()) - 1));
tbb::parallel_for(tbb::blocked_range<size_t>(0, n_raft_layers),
[&support_layers, &raft_layers, &intermediate_layers, &config, &support_params, &slicing_params,
&bbox_object, raft_angle_1st_layer, raft_angle_base, raft_angle_interface, link_max_length_factor]
&bbox_object, link_max_length_factor]
(const tbb::blocked_range<size_t>& range) {
for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id)
{
@ -4270,7 +4275,7 @@ void generate_support_toolpaths(
assert(!raft_layer.bridging);
if (! to_infill_polygons.empty()) {
Fill *filler = filler_support.get();
filler->angle = raft_angle_base;
filler->angle = support_params.raft_angle_base;
filler->spacing = support_params.support_material_flow.spacing();
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.support_density));
fill_expolygons_with_sheath_generate_paths(
@ -4293,11 +4298,11 @@ void generate_support_toolpaths(
float density = 0.f;
if (support_layer_id == 0) {
// Base flange.
filler->angle = raft_angle_1st_layer;
filler->angle = support_params.raft_angle_1st_layer;
filler->spacing = support_params.first_layer_flow.spacing();
density = float(config.raft_first_layer_density.value * 0.01);
} else if (support_layer_id >= slicing_params.base_raft_layers) {
filler->angle = raft_angle_interface;
filler->angle = support_params.raft_interface_angle(support_layer.interface_id());
// We don't use $base_flow->spacing because we need a constant spacing
// value that guarantees that all layers are correctly aligned.
filler->spacing = support_params.support_material_flow.spacing();
@ -4345,7 +4350,7 @@ void generate_support_toolpaths(
std::vector<LayerCache> layer_caches(support_layers.size());
tbb::parallel_for(tbb::blocked_range<size_t>(n_raft_layers, support_layers.size()),
[&config, &support_params, &support_layers, &bottom_contacts, &top_contacts, &intermediate_layers, &interface_layers, &base_interface_layers, &layer_caches, &loop_interface_processor,
[&config, &slicing_params, &support_params, &support_layers, &bottom_contacts, &top_contacts, &intermediate_layers, &interface_layers, &base_interface_layers, &layer_caches, &loop_interface_processor,
&bbox_object, &angles, n_raft_layers, link_max_length_factor]
(const tbb::blocked_range<size_t>& range) {
// Indices of the 1st layer in their respective container at the support layer height.
@ -4381,9 +4386,8 @@ void generate_support_toolpaths(
{
SupportLayer &support_layer = *support_layers[support_layer_id];
LayerCache &layer_cache = layer_caches[support_layer_id];
float interface_angle_delta = config.support_material_style.value != smsGrid ?
(support_layer.interface_id() & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.) :
0;
const float support_interface_angle = config.support_material_style.value == smsGrid ?
support_params.interface_angle : support_params.raft_interface_angle(support_layer.interface_id());
// Find polygons with the same print_z.
SupportGeneratorLayerExtruded &bottom_contact_layer = layer_cache.bottom_contact_layer;
@ -4412,7 +4416,8 @@ void generate_support_toolpaths(
if (idx_layer_intermediate < intermediate_layers.size() && intermediate_layers[idx_layer_intermediate]->print_z < support_layer.print_z + EPSILON)
base_layer.layer = intermediate_layers[idx_layer_intermediate];
bool raft_layer = support_layer_id == n_raft_layers;
// This layer is a raft contact layer. Any contact polygons at this layer are raft contacts.
bool raft_layer = slicing_params.interface_raft_layers && top_contact_layer.layer && is_approx(top_contact_layer.layer->print_z, slicing_params.raft_contact_top_z);
if (config.support_material_interface_layers == 0) {
// If no top interface layers were requested, we treat the contact layer exactly as a generic base layer.
// Don't merge the raft contact layer though.
@ -4470,7 +4475,9 @@ void generate_support_toolpaths(
// If zero interface layers are configured, use the same angle as for the base layers.
angles[support_layer_id % angles.size()] :
// Use interface angle for the interface layers.
support_params.interface_angle + interface_angle_delta;
raft_contact ?
support_params.raft_interface_angle(support_layer.interface_id()) :
support_interface_angle;
double density = raft_contact ? support_params.raft_interface_density : interface_as_base ? support_params.support_density : support_params.interface_density;
filler->spacing = raft_contact ? support_params.raft_interface_flow.spacing() :
interface_as_base ? support_params.support_material_flow.spacing() : support_params.support_material_interface_flow.spacing();
@ -4499,7 +4506,7 @@ void generate_support_toolpaths(
// the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b)
assert(! base_interface_layer.layer->bridging);
Flow interface_flow = support_params.support_material_flow.with_height(float(base_interface_layer.layer->height));
filler->angle = support_params.interface_angle + interface_angle_delta;
filler->angle = support_interface_angle;
filler->spacing = support_params.support_material_interface_flow.spacing();
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.interface_density));
fill_expolygons_generate_paths(
@ -4748,4 +4755,3 @@ sub clip_with_shape {
*/
} // namespace Slic3r

View File

@ -161,6 +161,14 @@ struct SupportParameters {
InfillPattern contact_fill_pattern;
// Shall the sparse (base) layers be printed with a single perimeter line (sheath) for robustness?
bool with_sheath;
float raft_angle_1st_layer;
float raft_angle_base;
float raft_angle_interface;
// Produce a raft interface angle for a given SupportLayer::interface_id()
float raft_interface_angle(size_t interface_id) const
{ return this->raft_angle_interface + ((interface_id & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.)); }
};
// Remove bridges from support contact areas.

View File

@ -901,7 +901,7 @@ std::tuple<SupportPoints, PartialObjects> check_stability(const PrintObject *po,
params)) {
if (bridge.support_point_generated.has_value()) {
reckon_new_support_point(*bridge.support_point_generated, create_support_point_position(bridge.b),
-EPSILON, Vec2f::Zero());
float(-EPSILON), Vec2f::Zero());
}
}
}
@ -916,7 +916,7 @@ std::tuple<SupportPoints, PartialObjects> check_stability(const PrintObject *po,
params);
for (const ExtrusionLine &perim : perims) {
if (perim.support_point_generated.has_value()) {
reckon_new_support_point(*perim.support_point_generated, create_support_point_position(perim.b), -EPSILON,
reckon_new_support_point(*perim.support_point_generated, create_support_point_position(perim.b), float(-EPSILON),
Vec2f::Zero());
}
if (perim.is_external_perimeter()) {

View File

@ -145,6 +145,7 @@ struct PartialObject
using PartialObjects = std::vector<PartialObject>;
// Both support points and partial objects are sorted from the lowest z to the highest
std::tuple<SupportPoints, PartialObjects> full_search(const PrintObject *po, const PrintTryCancel& cancel_func, const Params &params);
void estimate_supports_malformations(std::vector<SupportLayer *> &layers, float supports_flow_width, const Params &params);

View File

@ -33,40 +33,31 @@ class Surface
public:
SurfaceType surface_type;
ExPolygon expolygon;
double thickness; // in mm
unsigned short thickness_layers; // in layers
double bridge_angle; // in radians, ccw, 0 = East, only 0+ (negative means undefined)
unsigned short extra_perimeters;
double thickness { -1 }; // in mm
unsigned short thickness_layers { 1 }; // in layers
double bridge_angle { -1. }; // in radians, ccw, 0 = East, only 0+ (negative means undefined)
unsigned short extra_perimeters { 0 };
Surface(const Slic3r::Surface &rhs)
: surface_type(rhs.surface_type), expolygon(rhs.expolygon),
thickness(rhs.thickness), thickness_layers(rhs.thickness_layers),
bridge_angle(rhs.bridge_angle), extra_perimeters(rhs.extra_perimeters)
{};
Surface(SurfaceType _surface_type, const ExPolygon &_expolygon)
: surface_type(_surface_type), expolygon(_expolygon),
thickness(-1), thickness_layers(1), bridge_angle(-1), extra_perimeters(0)
{};
Surface(const Surface &other, const ExPolygon &_expolygon)
: surface_type(other.surface_type), expolygon(_expolygon),
thickness(other.thickness), thickness_layers(other.thickness_layers),
bridge_angle(other.bridge_angle), extra_perimeters(other.extra_perimeters)
{};
Surface(Surface &&rhs)
: surface_type(rhs.surface_type), expolygon(std::move(rhs.expolygon)),
thickness(rhs.thickness), thickness_layers(rhs.thickness_layers),
bridge_angle(rhs.bridge_angle), extra_perimeters(rhs.extra_perimeters)
{};
Surface(SurfaceType _surface_type, ExPolygon &&_expolygon)
: surface_type(_surface_type), expolygon(std::move(_expolygon)),
thickness(-1), thickness_layers(1), bridge_angle(-1), extra_perimeters(0)
{};
Surface(const Surface &other, ExPolygon &&_expolygon)
: surface_type(other.surface_type), expolygon(std::move(_expolygon)),
thickness(other.thickness), thickness_layers(other.thickness_layers),
bridge_angle(other.bridge_angle), extra_perimeters(other.extra_perimeters)
{};
Surface(const Slic3r::Surface &rhs) :
surface_type(rhs.surface_type), expolygon(rhs.expolygon),
thickness(rhs.thickness), thickness_layers(rhs.thickness_layers),
bridge_angle(rhs.bridge_angle), extra_perimeters(rhs.extra_perimeters) {}
Surface(SurfaceType surface_type, const ExPolygon &expolygon) :
surface_type(surface_type), expolygon(expolygon) {}
Surface(const Surface &templ, const ExPolygon &expolygon) :
surface_type(templ.surface_type), expolygon(expolygon),
thickness(templ.thickness), thickness_layers(templ.thickness_layers),
bridge_angle(templ.bridge_angle), extra_perimeters(templ.extra_perimeters) {}
Surface(Surface &&rhs) :
surface_type(rhs.surface_type), expolygon(std::move(rhs.expolygon)),
thickness(rhs.thickness), thickness_layers(rhs.thickness_layers),
bridge_angle(rhs.bridge_angle), extra_perimeters(rhs.extra_perimeters) {}
Surface(SurfaceType surface_type, ExPolygon &&expolygon) :
surface_type(surface_type), expolygon(std::move(expolygon)) {}
Surface(const Surface &templ, ExPolygon &&expolygon) :
surface_type(templ.surface_type), expolygon(std::move(expolygon)),
thickness(templ.thickness), thickness_layers(templ.thickness_layers),
bridge_angle(templ.bridge_angle), extra_perimeters(templ.extra_perimeters) {}
Surface& operator=(const Surface &rhs)
{

View File

@ -51,16 +51,12 @@ SurfacesPtr SurfaceCollection::filter_by_type(const SurfaceType type) const
return ss;
}
SurfacesPtr SurfaceCollection::filter_by_types(const SurfaceType *types, int ntypes) const
SurfacesPtr SurfaceCollection::filter_by_types(std::initializer_list<SurfaceType> types) const
{
SurfacesPtr ss;
for (const Surface &surface : this->surfaces)
for (int i = 0; i < ntypes; ++ i) {
if (surface.surface_type == types[i]) {
ss.push_back(&surface);
break;
}
}
if (std::find(types.begin(), types.end(), surface.surface_type) != types.end())
ss.push_back(&surface);
return ss;
}
@ -85,23 +81,15 @@ void SurfaceCollection::keep_type(const SurfaceType type)
surfaces.erase(surfaces.begin() + j, surfaces.end());
}
void SurfaceCollection::keep_types(const SurfaceType *types, int ntypes)
void SurfaceCollection::keep_types(std::initializer_list<SurfaceType> types)
{
size_t j = 0;
for (size_t i = 0; i < surfaces.size(); ++ i) {
bool keep = false;
for (int k = 0; k < ntypes; ++ k) {
if (surfaces[i].surface_type == types[k]) {
keep = true;
break;
}
}
if (keep) {
for (size_t i = 0; i < surfaces.size(); ++ i)
if (std::find(types.begin(), types.end(), surfaces[i].surface_type) != types.end()) {
if (j < i)
std::swap(surfaces[i], surfaces[j]);
++ j;
}
}
if (j < surfaces.size())
surfaces.erase(surfaces.begin() + j, surfaces.end());
}
@ -136,23 +124,15 @@ void SurfaceCollection::remove_type(const SurfaceType type, ExPolygons *polygons
surfaces.erase(surfaces.begin() + j, surfaces.end());
}
void SurfaceCollection::remove_types(const SurfaceType *types, int ntypes)
void SurfaceCollection::remove_types(std::initializer_list<SurfaceType> types)
{
size_t j = 0;
for (size_t i = 0; i < surfaces.size(); ++ i) {
bool remove = false;
for (int k = 0; k < ntypes; ++ k) {
if (surfaces[i].surface_type == types[k]) {
remove = true;
break;
}
}
if (! remove) {
for (size_t i = 0; i < surfaces.size(); ++ i)
if (std::find(types.begin(), types.end(), surfaces[i].surface_type) == types.end()) {
if (j < i)
std::swap(surfaces[i], surfaces[j]);
++ j;
}
}
if (j < surfaces.size())
surfaces.erase(surfaces.begin() + j, surfaces.end());
}

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