Skip to content

Commit

Permalink
only_one_perimeter_top for arachne by @vovodroid
Browse files Browse the repository at this point in the history
  • Loading branch information
supermerill committed Dec 27, 2023
2 parents 50386a0 + 34e7b44 commit d29c2ea
Show file tree
Hide file tree
Showing 8 changed files with 336 additions and 51 deletions.
116 changes: 116 additions & 0 deletions src/libslic3r/ClipperUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,124 @@ bool export_clipper_input_polygons_bin(const char *path, const ClipperLib::Paths
namespace ClipperUtils {
Points EmptyPathsProvider::s_empty_points;
Points SinglePathProvider::s_end;


// Clip source polygon to be used as a clipping polygon with a bouding box around the source (to be clipped)
// polygon. Useful as an optimization for expensive ClipperLib operations, for example when clipping source
// polygons one by one with a set of polygons covering the whole layer below.
template<typename PointsType>
inline void clip_clipper_polygon_with_subject_bbox_templ(const PointsType & src,
const BoundingBox &bbox,
PointsType & out)
{
using PointType = typename PointsType::value_type;
out.clear();
const size_t cnt = src.size();
if (cnt < 3)
return;
enum class Side { Left = 1, Right = 2, Top = 4, Bottom = 8 };
auto sides = [bbox](const PointType &p) {
return int(p.x() < bbox.min.x()) * int(Side::Left) + int(p.x() > bbox.max.x()) * int(Side::Right) +
int(p.y() < bbox.min.y()) * int(Side::Bottom) + int(p.y() > bbox.max.y()) * int(Side::Top);
};
int sides_prev = sides(src.back());
int sides_this = sides(src.front());
const size_t last = cnt - 1;
for (size_t i = 0; i < last; ++i) {
int sides_next = sides(src[i + 1]);
if ( // This point is inside. Take it.
sides_this == 0 ||
// Either this point is outside and previous or next is inside, or
// the edge possibly cuts corner of the bounding box.
(sides_prev & sides_this & sides_next) == 0) {
out.emplace_back(src[i]);
sides_prev = sides_this;
} else {
// All the three points (this, prev, next) are outside at the same side.
// Ignore this point.
}
sides_this = sides_next;
}
// Never produce just a single point output polygon.
if (!out.empty())
if (int sides_next = sides(out.front());
// The last point is inside. Take it.
sides_this == 0 ||
// Either this point is outside and previous or next is inside, or
// the edge possibly cuts corner of the bounding box.
(sides_prev & sides_this & sides_next) == 0)
out.emplace_back(src.back());
}
void clip_clipper_polygon_with_subject_bbox(const Points &src, const BoundingBox &bbox, Points &out)
{
clip_clipper_polygon_with_subject_bbox_templ(src, bbox, out);
}
void clip_clipper_polygon_with_subject_bbox(const ZPoints &src, const BoundingBox &bbox, ZPoints &out)
{
clip_clipper_polygon_with_subject_bbox_templ(src, bbox, out);
}
template<typename PointsType>
[[nodiscard]] PointsType clip_clipper_polygon_with_subject_bbox_templ(const PointsType & src,
const BoundingBox &bbox)
{
PointsType out;
clip_clipper_polygon_with_subject_bbox(src, bbox, out);
return out;
}
[[nodiscard]] Points clip_clipper_polygon_with_subject_bbox(const Points &src, const BoundingBox &bbox)
{
return clip_clipper_polygon_with_subject_bbox_templ(src, bbox);
}
[[nodiscard]] ZPoints clip_clipper_polygon_with_subject_bbox(const ZPoints &src, const BoundingBox &bbox)
{
return clip_clipper_polygon_with_subject_bbox_templ(src, bbox);
}
void clip_clipper_polygon_with_subject_bbox(const Polygon &src, const BoundingBox &bbox, Polygon &out)
{
clip_clipper_polygon_with_subject_bbox(src.points, bbox, out.points);
}
[[nodiscard]] Polygon clip_clipper_polygon_with_subject_bbox(const Polygon &src, const BoundingBox &bbox)
{
Polygon out;
clip_clipper_polygon_with_subject_bbox(src.points, bbox, out.points);
return out;
}
[[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const Polygons &src, const BoundingBox &bbox)
{
Polygons out;
out.reserve(src.size());
for (const Polygon &p : src) out.emplace_back(clip_clipper_polygon_with_subject_bbox(p, bbox));
out.erase(std::remove_if(out.begin(), out.end(), [](const Polygon &polygon) { return polygon.empty(); }),
out.end());
return out;
}
[[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const ExPolygon &src, const BoundingBox &bbox)
{
Polygons out;
out.reserve(src.num_contours());
out.emplace_back(clip_clipper_polygon_with_subject_bbox(src.contour, bbox));
for (const Polygon &p : src.holes) out.emplace_back(clip_clipper_polygon_with_subject_bbox(p, bbox));
out.erase(std::remove_if(out.begin(), out.end(), [](const Polygon &polygon) { return polygon.empty(); }),
out.end());
return out;
}
[[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const ExPolygons &src, const BoundingBox &bbox)
{
Polygons out;
out.reserve(number_polygons(src));
for (const ExPolygon &p : src) {
Polygons temp = clip_clipper_polygons_with_subject_bbox(p, bbox);
out.insert(out.end(), temp.begin(), temp.end());
}

out.erase(std::remove_if(out.begin(), out.end(), [](const Polygon &polygon) { return polygon.empty(); }),
out.end());
return out;
}
}



static ExPolygons PolyTreeToExPolygons(ClipperLib::PolyTree &&polytree)
{
struct Inner {
Expand Down
18 changes: 17 additions & 1 deletion src/libslic3r/ClipperUtils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,23 @@ namespace ClipperUtils {
const SurfacesPtr &m_surfaces;
size_t m_size;
};
}

using ZPoint = Vec3i32;
using ZPoints = std::vector<Vec3i32>;

// Clip source polygon to be used as a clipping polygon with a bouding box around the source (to be clipped) polygon.
// Useful as an optimization for expensive ClipperLib operations, for example when clipping source polygons one by
// one with a set of polygons covering the whole layer below.
void clip_clipper_polygon_with_subject_bbox(const Points &src, const BoundingBox &bbox, Points &out);
void clip_clipper_polygon_with_subject_bbox(const ZPoints &src, const BoundingBox &bbox, ZPoints &out);
[[nodiscard]] Points clip_clipper_polygon_with_subject_bbox(const Points &src, const BoundingBox &bbox);
[[nodiscard]] ZPoints clip_clipper_polygon_with_subject_bbox(const ZPoints &src, const BoundingBox &bbox);
void clip_clipper_polygon_with_subject_bbox(const Polygon &src, const BoundingBox &bbox, Polygon &out);
[[nodiscard]] Polygon clip_clipper_polygon_with_subject_bbox(const Polygon &src, const BoundingBox &bbox);
[[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const Polygons &src, const BoundingBox &bbox);
[[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const ExPolygon &src, const BoundingBox &bbox);
[[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const ExPolygons &src, const BoundingBox &bbox);
}

// Perform union of input polygons using the non-zero rule, convert to ExPolygons.
ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input, bool do_union = false);
Expand Down
4 changes: 2 additions & 2 deletions src/libslic3r/GCode/CoolingBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -506,9 +506,9 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
line.length = std::abs(dif[3]);
}
line.feedrate = new_pos[4];
if (line.feedrate > 0.f && line.length > 0.f) {
if (line.feedrate > 0.f && /*line.length*/ dxy2 > 0.f) {
assert((line.type & CoolingLine::TYPE_ADJUSTABLE) == 0);
assert(active_speed_modifier != size_t(-1));
//assert(active_speed_modifier != size_t(-1)); // can happen in custom gcode.
line.type |= current_stamp;
}
assert((line.type & CoolingLine::TYPE_ADJUSTABLE) == 0 || line.feedrate > 0.f);
Expand Down
136 changes: 135 additions & 1 deletion src/libslic3r/PerimeterGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,43 @@ ProcessSurfaceResult PerimeterGenerator::process_arachne(int& loop_number, const

Polygons last_p = to_polygons(last);

Arachne::WallToolPaths wallToolPaths(last_p, this->get_ext_perimeter_spacing(), this->get_ext_perimeter_width(), this->get_perimeter_spacing(), this->get_perimeter_width(), coord_t(loop_number + 1), 0, this->layer->height, *this->object_config, *this->print_config);
// only_one_perimeter_top, from orca
std::vector<Arachne::VariableWidthLines> out_shell;
if (loop_number > 0 && this->config->only_one_perimeter_top && !surface.has_mod_bridge() && upper_slices != nullptr) {
// Check if current layer has surfaces that are not covered by upper layer (i.e., top surfaces)
ExPolygons non_top_polygons;
ExPolygons fill_clip;

split_top_surfaces(lower_slices, upper_slices, last, result.top_fills, non_top_polygons, fill_clip);

if (result.top_fills.empty()) {
// No top surfaces, no special handling needed
} else {
// First we slice the outer shell
Polygons last_p = to_polygons(last);
Arachne::WallToolPaths wallToolPaths(last_p, this->get_ext_perimeter_spacing(),this->get_ext_perimeter_width(),
this->get_perimeter_spacing(), this->get_perimeter_width(), coord_t(1), 0,
this->layer->height, *this->object_config, *this->print_config);
out_shell = wallToolPaths.getToolPaths();
// Make sure infill not overlap with wall
result.top_fills = intersection_ex(result.top_fills, wallToolPaths.getInnerContour());

if (!result.top_fills.empty()) {
// Then get the inner part that needs more walls
last = intersection_ex(non_top_polygons, wallToolPaths.getInnerContour());
loop_number = 0;
} else {
// Give up the outer shell because we don't have any meaningful top surface
out_shell.clear();
}
}
}



Arachne::WallToolPaths wallToolPaths(last_p, this->get_ext_perimeter_spacing(), this->get_ext_perimeter_width(),
this->get_perimeter_spacing(), this->get_perimeter_width(), coord_t(loop_number + 1), 0,
this->layer->height, *this->object_config, *this->print_config);
std::vector<Arachne::VariableWidthLines> perimeters = wallToolPaths.getToolPaths();
loop_number = int(perimeters.size()) - 1;

Expand Down Expand Up @@ -372,6 +408,104 @@ ProcessSurfaceResult PerimeterGenerator::process_arachne(int& loop_number, const
return result;
}

//TODO: merge with 'store surface for top infill if only_one_perimeter_top'
void PerimeterGenerator::split_top_surfaces(const ExPolygons *lower_slices,
const ExPolygons *upper_slices,
const ExPolygons &orig_polygons,
ExPolygons & top_fills,
ExPolygons & non_top_polygons,
ExPolygons & fill_clip)
{
// other perimeters
coord_t perimeter_width = this->perimeter_flow.scaled_width();
coord_t perimeter_spacing = this->perimeter_flow.scaled_spacing();

// external perimeters
coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width();
coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing();

bool has_gap_fill = false; // this->config->gap_infill_speed.value > 0;

// split the polygons with top/not_top
// get the offset from solid surface anchor*
const int32_t peri_count = this->config->perimeters.value;
coord_t offset_top_surface = scale_t(
1.5 * (peri_count == 0 ?
0. : unscaled(double(ext_perimeter_width + perimeter_spacing * int(peri_count - int(1))))));
// if possible, try to not push the extra perimeters inside the sparse infill
if (offset_top_surface > 0.9 * (this->config->perimeters <= 1 ? 0. : (perimeter_spacing * (peri_count - 1))))
offset_top_surface -= coord_t(0.9 * (peri_count <= 1 ? 0. : (perimeter_spacing * (peri_count - 1))));
else
offset_top_surface = 0;
// don't takes into account too thin areas
// skip if the exposed area is smaller than "min_width_top_surface"
coordf_t min_width_top_surface = std::max(coordf_t(ext_perimeter_spacing / 2 + 10),
scale_d(this->config->min_width_top_surface.get_abs_value(unscaled(perimeter_width))));

Polygons grown_upper_slices = offset(*upper_slices, min_width_top_surface);

// get boungding box of last
BoundingBox last_box = get_extents(orig_polygons);
last_box.offset(SCALED_EPSILON);

// get the Polygons upper the polygon this layer
Polygons upper_polygons_series_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(grown_upper_slices, last_box);

// set the clip to a virtual "second perimeter"
fill_clip = offset_ex(orig_polygons, -double(ext_perimeter_spacing));
// get the real top surface
ExPolygons grown_lower_slices;
ExPolygons bridge_checker;
auto nozzle_diameter = 0.4; // this->print_config->nozzle_diameter.get_at(this->config->wall_filament - 1);
// Check whether surface be bridge or not
if (lower_slices != NULL) {
// BBS: get the Polygons below the polygon this layer
Polygons lower_polygons_series_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*lower_slices, last_box);
coordf_t bridge_offset = std::max(coordf_t(ext_perimeter_spacing), (coordf_t(perimeter_width)));
// SoftFever: improve bridging
const coordf_t bridge_margin = scale_d(this->config->bridged_infill_margin.get_abs_value(unscaled(perimeter_width)));
bridge_checker = offset_ex(diff_ex(orig_polygons, lower_polygons_series_clipped, ApplySafetyOffset::Yes),
1.5 * bridge_offset + bridge_margin + perimeter_spacing / 2);
}
ExPolygons delete_bridge = diff_ex(orig_polygons, bridge_checker, ApplySafetyOffset::Yes);

ExPolygons top_polygons = diff_ex(delete_bridge, upper_polygons_series_clipped, ApplySafetyOffset::Yes);
// get the not-top surface, from the "real top" but enlarged by external_infill_margin (and the
// min_width_top_surface we removed a bit before)
ExPolygons temp_gap = diff_ex(top_polygons, fill_clip);
ExPolygons inner_polygons = diff_ex(orig_polygons,
offset_ex(top_polygons, offset_top_surface + min_width_top_surface -
double(ext_perimeter_spacing / 2)),
ApplySafetyOffset::Yes);
// get the enlarged top surface, by using inner_polygons instead of upper_slices, and clip it for it to be exactly
// the polygons to fill.
top_polygons = diff_ex(fill_clip, inner_polygons, ApplySafetyOffset::Yes);
// increase by half peri the inner space to fill the frontier between last and stored.
top_fills = union_ex(top_fills, top_polygons);
// set the clip to the external wall but go back inside by infill_extrusion_width/2 to be sure the extrusion won't
// go outside even with a 100% overlap.
double infill_spacing_unscaled = this->config->infill_extrusion_width.get_abs_value(nozzle_diameter);
if (infill_spacing_unscaled == 0)
infill_spacing_unscaled = Flow::auto_extrusion_width(frInfill, nozzle_diameter);
fill_clip = offset_ex(orig_polygons, double(ext_perimeter_spacing / 2) - scale_(infill_spacing_unscaled / 2));
// ExPolygons oldLast = last;

non_top_polygons = intersection_ex(inner_polygons, orig_polygons);
if (has_gap_fill)
non_top_polygons = union_ex(non_top_polygons, temp_gap);
//{
// std::stringstream stri;
// stri << this->layer_id << "_1_"<< i <<"_only_one_peri"<< ".svg";
// SVG svg(stri.str());
// svg.draw(to_polylines(top_fills), "green");
// svg.draw(to_polylines(inner_polygons), "yellow");
// svg.draw(to_polylines(top_polygons), "cyan");
// svg.draw(to_polylines(oldLast), "orange");
// svg.draw(to_polylines(last), "red");
// svg.Close();
//}
}

void PerimeterGenerator::process()
{
// other perimeters
Expand Down
4 changes: 3 additions & 1 deletion src/libslic3r/PerimeterGenerator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,9 @@ class PerimeterGenerator {
ExtrusionLoop _extrude_and_cut_loop(const PerimeterGeneratorLoop& loop, const Point entryPoint, const Line& direction = Line(Point(0, 0), Point(0, 0)), bool enforce_loop = false) const;
// sub-function of _traverse_and_join_loops, find the good splot to cut a loop to be able to join it with an other one
PerimeterIntersectionPoint _get_nearest_point(const PerimeterGeneratorLoops &children, ExtrusionLoop &myPolylines, const coord_t dist_cut, const coord_t max_dist) const;

// for archne, to have one_peri_ont_op
void split_top_surfaces(const ExPolygons *lower_slices, const ExPolygons *upper_slices, const ExPolygons &orig_polygons,
ExPolygons & top_fills, ExPolygons & non_top_polygons, ExPolygons & fill_clip);

};

Expand Down
4 changes: 2 additions & 2 deletions src/slic3r/GUI/ConfigManipulation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -357,12 +357,12 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
"thin_perimeters", "overhangs_reverse", "perimeter_round_corners"})
toggle_field(el, have_perimeters && !have_arachne);

toggle_field("only_one_perimeter_top", have_perimeters); // with arachne, it will only do it for the last layer
toggle_field("only_one_perimeter_top", have_perimeters);
toggle_field("only_one_perimeter_first_layer", config->opt_int("perimeters") > 1);
toggle_field("overhangs_width", config->option<ConfigOptionFloatOrPercent>("overhangs_width_speed")->value > 0);
toggle_field("overhangs_reverse_threshold", have_perimeters && config->opt_bool("overhangs_reverse"));
toggle_field("overhangs_speed_enforce", have_perimeters && !config->opt_bool("perimeter_loop"));
toggle_field("min_width_top_surface", have_perimeters && config->opt_bool("only_one_perimeter_top") && !have_arachne);
toggle_field("min_width_top_surface", have_perimeters && config->opt_bool("only_one_perimeter_top"));
toggle_field("thin_perimeters_all", have_perimeters && config->option("thin_perimeters")->get_float() != 0 && !have_arachne);
bool have_thin_wall = !have_arachne && have_perimeters;
toggle_field("thin_walls", have_thin_wall);
Expand Down
Loading

0 comments on commit d29c2ea

Please sign in to comment.