Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ported automatic hole to polyhole conversion from superslicer #2336

Merged
merged 1 commit into from
Oct 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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