Skip to content

Commit

Permalink
Ported automatic hole to polyhole conversion from superslicer (#2336)
Browse files Browse the repository at this point in the history
I've ported automatic polyhole conversion from super slicer.

I'm happy to change the PR as required.

Closes #626
  • Loading branch information
SoftFever authored Oct 7, 2023
2 parents 8d0bb25 + 3359dad commit cd17209
Show file tree
Hide file tree
Showing 10 changed files with 189 additions and 2 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ You can download Orca Slicer here: [github releases page](https://github.com/Sof
- Auto calibrations for all printers
- Sandwich(inner-outer-inner) mode - an improved version of the `External perimeters first` mode
- Precise wall
- Polyholes conversion support [SuperSlicer Wiki: Polyholes](https://github.com/supermerill/SuperSlicer/wiki/Polyholes)
- Klipper support
- More granular controls
- More features can be found in [change notes](https://github.com/SoftFever/OrcaSlicer/releases/)
Expand Down
2 changes: 2 additions & 0 deletions src/libslic3r/Point.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,8 @@ class Point : public Vec2crd
double ccw_angle(const Point &p1, const Point &p2) const;
Point projection_onto(const MultiPoint &poly) const;
Point projection_onto(const Line &line) const;

double distance_to(const Point &point) const { return (point - *this).cast<double>().norm(); }
};

inline bool operator<(const Point &l, const Point &r)
Expand Down
4 changes: 2 additions & 2 deletions src/libslic3r/Preset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -765,8 +765,8 @@ static std::vector<std::string> s_Preset_print_options {
"initial_layer_travel_speed", "exclude_object", "slow_down_layers", "infill_anchor", "infill_anchor_max","initial_layer_min_bead_width",
"make_overhang_printable", "make_overhang_printable_angle", "make_overhang_printable_hole_size" ,"notes",
"wipe_tower_cone_angle", "wipe_tower_extra_spacing", "wipe_tower_extruder", "wiping_volumes_extruders","wipe_tower_bridging", "single_extruder_multi_material_priming",
"wipe_tower_rotation_angle", "tree_support_branch_distance_organic", "tree_support_branch_diameter_organic", "tree_support_branch_angle_organic"

"wipe_tower_rotation_angle", "tree_support_branch_distance_organic", "tree_support_branch_diameter_organic", "tree_support_branch_angle_organic",
"hole_to_polyhole", "hole_to_polyhole_threshold", "hole_to_polyhole_twisted"
};

static std::vector<std::string> s_Preset_filament_options {
Expand Down
2 changes: 2 additions & 0 deletions src/libslic3r/Print.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,8 @@ class PrintObject : public PrintObjectBaseWithState<Print, PrintObjectStep, posC
void detect_overhangs_for_lift();
void clear_overhangs_for_lift();

void _transform_hole_to_polyholes();

// Has any support (not counting the raft).
void detect_surfaces_type();
void process_external_surfaces();
Expand Down
28 changes: 28 additions & 0 deletions src/libslic3r/PrintConfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4153,6 +4153,34 @@ def = this->add("filament_loading_speed", coFloats);
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0));

def = this->add("hole_to_polyhole", coBool);
def->label = L("Convert holes to polyholes");
def->category = L("Quality");
def->tooltip = L("Search for almost-circular holes that span more than one layer and convert the geometry to polyholes."
" Use the nozzle size and the (biggest) diameter to compute the polyhole."
"\nSee http://hydraraptor.blogspot.com/2011/02/polyholes.html");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false));

def = this->add("hole_to_polyhole_threshold", coFloatOrPercent);
def->label = L("Polyhole detection margin");
def->category = L("Quality");
def->tooltip = L("Maximum defection of a point to the estimated radius of the circle."
"\nAs cylinders are often exported as triangles of varying size, points may not be on the circle circumference."
" This setting allows you some leway to broaden the detection."
"\nIn mm or in % of the radius.");
def->sidetext = L("mm or %");
def->max_literal = 10;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloatOrPercent(0.01, false));

def = this->add("hole_to_polyhole_twisted", coBool);
def->label = L("Polyhole twist");
def->category = L("Quality");
def->tooltip = L("Rotate the polyhole every layer.");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(true));

def = this->add("thumbnails", coPoints);
def->label = L("G-code thumbnails");
def->tooltip = L("Picture sizes to be stored into a .gcode and .sl1 / .sl1s files, in the following format: \"XxY, XxY, ...\"");
Expand Down
3 changes: 3 additions & 0 deletions src/libslic3r/PrintConfig.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,9 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionBool, make_overhang_printable))
((ConfigOptionBool, extra_perimeters_on_overhangs))
((ConfigOptionBool, slowdown_for_curled_perimeters))
((ConfigOptionBool, hole_to_polyhole))
((ConfigOptionFloatOrPercent, hole_to_polyhole_threshold))
((ConfigOptionBool, hole_to_polyhole_twisted))
)

PRINT_CONFIG_CLASS_DEFINE(
Expand Down
142 changes: 142 additions & 0 deletions src/libslic3r/PrintObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,145 @@ std::vector<std::reference_wrapper<const PrintRegion>> PrintObject::all_regions(
return out;
}

Polygons create_polyholes(const Point center, const coord_t radius, const coord_t nozzle_diameter, bool multiple)
{
// n = max(round(2 * d), 3); // for 0.4mm nozzle
size_t nb_edges = (int)std::max(3, (int)std::round(4.0 * unscaled(radius) * 0.4 / unscaled(nozzle_diameter)));
// cylinder(h = h, r = d / cos (180 / n), $fn = n);
//create x polyholes by rotation if multiple
int nb_polyhole = 1;
float rotation = 0;
if (multiple) {
nb_polyhole = 5;
rotation = 2 * float(PI) / (nb_edges * nb_polyhole);
}
Polygons list;
for (int i_poly = 0; i_poly < nb_polyhole; i_poly++)
list.emplace_back();
for (int i_poly = 0; i_poly < nb_polyhole; i_poly++) {
Polygon& pts = (((i_poly % 2) == 0) ? list[i_poly / 2] : list[(nb_polyhole + 1) / 2 + i_poly / 2]);
const float new_radius = radius / float(std::cos(PI / nb_edges));
for (size_t i_edge = 0; i_edge < nb_edges; ++i_edge) {
float angle = rotation * i_poly + (float(PI) * 2 * (float)i_edge) / nb_edges;
pts.points.emplace_back(center.x() + new_radius * cos(angle), center.y() + new_radius * sin(angle));
}
pts.make_clockwise();
}
//alternate
return list;
}

// Detect and convert holes to polyholes, implementation is ported from SuperSlicer
void PrintObject::_transform_hole_to_polyholes()
{
// get all circular holes for each layer
// the id is center-diameter-extruderid
//the tuple is Point center; float diameter_max; int extruder_id; coord_t max_variation; bool twist;
std::vector<std::vector<std::pair<std::tuple<Point, float, int, coord_t, bool>, Polygon*>>> layerid2center;
for (size_t i = 0; i < this->m_layers.size(); i++) layerid2center.emplace_back();
tbb::parallel_for(
tbb::blocked_range<size_t>(0, m_layers.size()),
[this, &layerid2center](const tbb::blocked_range<size_t>& range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
m_print->throw_if_canceled();
Layer* layer = m_layers[layer_idx];
for (size_t region_idx = 0; region_idx < layer->m_regions.size(); ++region_idx)
{
if (layer->m_regions[region_idx]->region().config().hole_to_polyhole) {
for (Surface& surf : layer->m_regions[region_idx]->slices.surfaces) {
for (Polygon& hole : surf.expolygon.holes) {
//test if convex (as it's clockwise bc it's a hole, we have to do the opposite)
if (hole.convex_points(PI).empty() && hole.points.size() > 8) {
// Computing circle center
Point center = hole.centroid();
double diameter_min = std::numeric_limits<float>::max(), diameter_max = 0;
double diameter_sum = 0;
for (int i = 0; i < hole.points.size(); ++i) {
double dist = hole.points[i].distance_to(center);
diameter_min = std::min(diameter_min, dist);
diameter_max = std::max(diameter_max, dist);
diameter_sum += dist;
}
//also use center of lines to check it's not a rectangle
double diameter_line_min = std::numeric_limits<float>::max(), diameter_line_max = 0;
Lines hole_lines = hole.lines();
for (Line l : hole_lines) {
Point midline = (l.a + l.b) / 2;
double dist = center.distance_to(midline);
diameter_line_min = std::min(diameter_line_min, dist);
diameter_line_max = std::max(diameter_line_max, dist);
}


// SCALED_EPSILON was a bit too harsh. Now using a config, as some may want some harsh setting and some don't.
coord_t max_variation = std::max(SCALED_EPSILON, scale_(this->m_layers[layer_idx]->m_regions[region_idx]->region().config().hole_to_polyhole_threshold.get_abs_value(unscaled(diameter_sum / hole.points.size()))));
bool twist = this->m_layers[layer_idx]->m_regions[region_idx]->region().config().hole_to_polyhole_twisted.value;
if (diameter_max - diameter_min < max_variation * 2 && diameter_line_max - diameter_line_min < max_variation * 2) {
layerid2center[layer_idx].emplace_back(
std::tuple<Point, float, int, coord_t, bool>{center, diameter_max, layer->m_regions[region_idx]->region().config().wall_filament.value, max_variation, twist}, & hole);
}
}
}
}
}
}
// for layer->slices, it will be also replaced later.
}
});
//sort holes per center-diameter
std::map<std::tuple<Point, float, int, coord_t, bool>, std::vector<std::pair<Polygon*, int>>> id2layerz2hole;

//search & find hole that span at least X layers
const size_t min_nb_layers = 2;
for (size_t layer_idx = 0; layer_idx < this->m_layers.size(); ++layer_idx) {
for (size_t hole_idx = 0; hole_idx < layerid2center[layer_idx].size(); ++hole_idx) {
//get all other same polygons
std::tuple<Point, float, int, coord_t, bool>& id = layerid2center[layer_idx][hole_idx].first;
float max_z = layers()[layer_idx]->print_z;
std::vector<std::pair<Polygon*, int>> holes;
holes.emplace_back(layerid2center[layer_idx][hole_idx].second, layer_idx);
for (size_t search_layer_idx = layer_idx + 1; search_layer_idx < this->m_layers.size(); ++search_layer_idx) {
if (layers()[search_layer_idx]->print_z - layers()[search_layer_idx]->height - max_z > EPSILON) break;
//search an other polygon with same id
for (size_t search_hole_idx = 0; search_hole_idx < layerid2center[search_layer_idx].size(); ++search_hole_idx) {
std::tuple<Point, float, int, coord_t, bool>& search_id = layerid2center[search_layer_idx][search_hole_idx].first;
if (std::get<2>(id) == std::get<2>(search_id)
&& std::get<0>(id).distance_to(std::get<0>(search_id)) < std::get<3>(id)
&& std::abs(std::get<1>(id) - std::get<1>(search_id)) < std::get<3>(id)
) {
max_z = layers()[search_layer_idx]->print_z;
holes.emplace_back(layerid2center[search_layer_idx][search_hole_idx].second, search_layer_idx);
layerid2center[search_layer_idx].erase(layerid2center[search_layer_idx].begin() + search_hole_idx);
search_hole_idx--;
break;
}
}
}
//check if strait hole or first layer hole (cause of first layer compensation)
if (holes.size() >= min_nb_layers || (holes.size() == 1 && holes[0].second == 0)) {
id2layerz2hole.emplace(std::move(id), std::move(holes));
}
}
}
//create a polyhole per id and replace holes points by it.
for (auto entry : id2layerz2hole) {
Polygons polyholes = create_polyholes(std::get<0>(entry.first), std::get<1>(entry.first), scale_(print()->config().nozzle_diameter.get_at(std::get<2>(entry.first) - 1)), std::get<4>(entry.first));
for (auto& poly_to_replace : entry.second) {
Polygon polyhole = polyholes[poly_to_replace.second % polyholes.size()];
//search the clone in layers->slices
for (ExPolygon& explo_slice : m_layers[poly_to_replace.second]->lslices) {
for (Polygon& poly_slice : explo_slice.holes) {
if (poly_slice.points == poly_to_replace.first->points) {
poly_slice.points = polyhole.points;
}
}
}
// copy
poly_to_replace.first->points = polyhole.points;
}
}
}

// 1) Merges typed region slices into stInternal type.
// 2) Increases an "extra perimeters" counter at region slices where needed.
// 3) Generates perimeters, gap fills and fill regions (fill regions of type stInternal).
Expand Down Expand Up @@ -816,6 +955,9 @@ bool PrintObject::invalidate_state_by_config_options(
|| opt_key == "max_bridge_length"
|| opt_key == "support_interface_top_layers"
|| opt_key == "support_critical_regions_only"
|| opt_key == "hole_to_polyhole"
|| opt_key == "hole_to_polyhole_threshold"
|| opt_key == "hole_to_polyhole_twisted"
) {
steps.emplace_back(posSlice);
} else if (opt_key == "enable_support") {
Expand Down
3 changes: 3 additions & 0 deletions src/libslic3r/PrintObjectSlice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,9 @@ void PrintObject::slice()
}
#endif

// Detect and process holes that should be converted to polyholes
this->_transform_hole_to_polyholes();

// BBS: the actual first layer slices stored in layers are re-sorted by volume group and will be used to generate brim
groupingVolumesForBrim(this, m_layers, firstLayerReplacedBy);

Expand Down
3 changes: 3 additions & 0 deletions src/slic3r/GUI/ConfigManipulation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,9 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co
toggle_line("exclude_object", gcflavor == gcfKlipper);

toggle_line("min_width_top_surface",config->opt_bool("only_one_wall_top"));

for (auto el : { "hole_to_polyhole_threshold", "hole_to_polyhole_twisted" })
toggle_line(el, config->opt_bool("hole_to_polyhole"));
}

void ConfigManipulation::update_print_sla_config(DynamicPrintConfig* config, const bool is_global_config/* = false*/)
Expand Down
3 changes: 3 additions & 0 deletions src/slic3r/GUI/Tab.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1870,6 +1870,9 @@ void TabPrint::build()
optgroup->append_single_option_line("elefant_foot_compensation");
optgroup->append_single_option_line("elefant_foot_compensation_layers");
optgroup->append_single_option_line("precise_outer_wall");
optgroup->append_single_option_line("hole_to_polyhole");
optgroup->append_single_option_line("hole_to_polyhole_threshold");
optgroup->append_single_option_line("hole_to_polyhole_twisted");

optgroup = page->new_optgroup(L("Ironing"), L"param_ironing");
optgroup->append_single_option_line("ironing_type");
Expand Down

0 comments on commit cd17209

Please sign in to comment.