Make Overhang Printable (#1615)
* First working conical overhang impl * Add settings for Make overhang printable * Added an Advanced setting to the Quality menu for Make overhang printable * Allow toggling the feature and adjusting the angle threshold * Retain recesses in the bottom of the model when "Make Overhangs Printable" is turned on * Add settings for Make overhang printable hole size * Allow disabling conical overhang per modifier * Should check upper layer instead * Skip unnecessary layers * Apply conical overhang before any size compensation which fixs the `lslices` not being updated issue and also the elephant foot compensation * 1. Fix an issue that changing make_overhang_printable related parameters won't clear caches properly. 2. Tweak the logic of make_overhang_printable parameter so that it works for per region/object/print level. Remove make_overhang_printable_disable parameter. * Change default make_overhang_printable_angle to 55 * One more missing commit * 1. Skip checking default_region_config()->make_overhang_printable 2. Handle make_overhang_printable_angle and make_overhang_printable_hole_size value change --------- Co-authored-by: David Bern <odie5533@users.noreply.github.com> Co-authored-by: SoftFever <softfeverever@gmail.com>
This commit is contained in:
parent
ab6649da8a
commit
b571318be8
11 changed files with 164 additions and 5 deletions
|
@ -787,7 +787,11 @@ Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons& poly1, const Slic3r::ExPol
|
||||||
for (const ExPolygon& expoly : poly2)
|
for (const ExPolygon& expoly : poly2)
|
||||||
expolys.push_back(expoly);
|
expolys.push_back(expoly);
|
||||||
return union_ex(expolys);
|
return union_ex(expolys);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Slic3r::ExPolygons xor_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset) {
|
||||||
|
return _clipper_ex(ClipperLib::ctXor, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::ExPolygonProvider(clip), do_safety_offset);
|
||||||
|
}
|
||||||
|
|
||||||
template<typename PathsProvider1, typename PathsProvider2>
|
template<typename PathsProvider1, typename PathsProvider2>
|
||||||
Polylines _clipper_pl_open(ClipperLib::ClipType clipType, PathsProvider1 &&subject, PathsProvider2 &&clip)
|
Polylines _clipper_pl_open(ClipperLib::ClipType clipType, PathsProvider1 &&subject, PathsProvider2 &&clip)
|
||||||
|
|
|
@ -529,6 +529,8 @@ Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons& poly1, const Slic3r::ExPol
|
||||||
ClipperLib::PolyTree union_pt(const Slic3r::Polygons &subject);
|
ClipperLib::PolyTree union_pt(const Slic3r::Polygons &subject);
|
||||||
ClipperLib::PolyTree union_pt(const Slic3r::ExPolygons &subject);
|
ClipperLib::PolyTree union_pt(const Slic3r::ExPolygons &subject);
|
||||||
|
|
||||||
|
Slic3r::ExPolygons xor_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||||
|
|
||||||
Slic3r::Polygons union_pt_chained_outside_in(const Slic3r::Polygons &subject);
|
Slic3r::Polygons union_pt_chained_outside_in(const Slic3r::Polygons &subject);
|
||||||
|
|
||||||
ClipperLib::PolyNodes order_nodes(const ClipperLib::PolyNodes &nodes);
|
ClipperLib::PolyNodes order_nodes(const ClipperLib::PolyNodes &nodes);
|
||||||
|
|
|
@ -756,7 +756,8 @@ static std::vector<std::string> s_Preset_print_options {
|
||||||
"bridge_density", "precise_outer_wall", "overhang_speed_classic", "bridge_acceleration",
|
"bridge_density", "precise_outer_wall", "overhang_speed_classic", "bridge_acceleration",
|
||||||
"sparse_infill_acceleration", "internal_solid_infill_acceleration", "tree_support_adaptive_layer_height", "tree_support_auto_brim",
|
"sparse_infill_acceleration", "internal_solid_infill_acceleration", "tree_support_adaptive_layer_height", "tree_support_auto_brim",
|
||||||
"tree_support_brim_width", "gcode_comments", "gcode_label_objects",
|
"tree_support_brim_width", "gcode_comments", "gcode_label_objects",
|
||||||
"initial_layer_travel_speed", "exclude_object", "slow_down_layers", "infill_anchor", "infill_anchor_max"
|
"initial_layer_travel_speed", "exclude_object", "slow_down_layers", "infill_anchor", "infill_anchor_max",
|
||||||
|
"make_overhang_printable", "make_overhang_printable_angle", "make_overhang_printable_hole_size"
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -521,6 +521,7 @@ private:
|
||||||
//
|
//
|
||||||
// object id
|
// object id
|
||||||
size_t m_id;
|
size_t m_id;
|
||||||
|
void apply_conical_overhang();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
//BBS: When printing multi-material objects, this settings will make slicer to clip the overlapping object parts one by the other.
|
//BBS: When printing multi-material objects, this settings will make slicer to clip the overlapping object parts one by the other.
|
||||||
|
|
|
@ -2371,6 +2371,35 @@ void PrintConfigDef::init_fff_params()
|
||||||
def->mode = comAdvanced;
|
def->mode = comAdvanced;
|
||||||
def->set_default_value(new ConfigOptionString("{input_filename_base}_{filament_type[0]}_{print_time}.gcode"));
|
def->set_default_value(new ConfigOptionString("{input_filename_base}_{filament_type[0]}_{print_time}.gcode"));
|
||||||
|
|
||||||
|
def = this->add("make_overhang_printable", coBool);
|
||||||
|
def->label = L("Make overhang printable");
|
||||||
|
def->category = L("Quality");
|
||||||
|
def->tooltip = L("Modify the geometry to print overhangs without support material.");
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->set_default_value(new ConfigOptionBool(false));
|
||||||
|
|
||||||
|
def = this->add("make_overhang_printable_angle", coFloat);
|
||||||
|
def->label = L("Make overhang printable maximum angle");
|
||||||
|
def->category = L("Quality");
|
||||||
|
def->tooltip = L("Maximum angle of overhangs to allow after making more steep overhangs printable."
|
||||||
|
"90° will not change the model at all and allow any overhang, while 0 will "
|
||||||
|
"replace all overhangs with conical material.");
|
||||||
|
def->sidetext = L("°");
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->min = 0.;
|
||||||
|
def->max = 90.;
|
||||||
|
def->set_default_value(new ConfigOptionFloat(55.));
|
||||||
|
|
||||||
|
def = this->add("make_overhang_printable_hole_size", coFloat);
|
||||||
|
def->label = L("Make overhang printable hole area");
|
||||||
|
def->category = L("Quality");
|
||||||
|
def->tooltip = L("Maximum area of a hole in the base of the model before it's filled by conical material."
|
||||||
|
"A value of 0 will fill all the holes in the model base.");
|
||||||
|
def->sidetext = L("mm²");
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->min = 0.;
|
||||||
|
def->set_default_value(new ConfigOptionFloat(0.));
|
||||||
|
|
||||||
def = this->add("detect_overhang_wall", coBool);
|
def = this->add("detect_overhang_wall", coBool);
|
||||||
def->label = L("Detect overhang wall");
|
def->label = L("Detect overhang wall");
|
||||||
def->category = L("Quality");
|
def->category = L("Quality");
|
||||||
|
|
|
@ -713,6 +713,10 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||||
((ConfigOptionInt, wall_distribution_count))
|
((ConfigOptionInt, wall_distribution_count))
|
||||||
((ConfigOptionPercent, min_feature_size))
|
((ConfigOptionPercent, min_feature_size))
|
||||||
((ConfigOptionPercent, min_bead_width))
|
((ConfigOptionPercent, min_bead_width))
|
||||||
|
|
||||||
|
// Orca
|
||||||
|
((ConfigOptionFloat, make_overhang_printable_angle))
|
||||||
|
((ConfigOptionFloat, make_overhang_printable_hole_size))
|
||||||
)
|
)
|
||||||
|
|
||||||
// This object is mapped to Perl as Slic3r::Config::PrintRegion.
|
// This object is mapped to Perl as Slic3r::Config::PrintRegion.
|
||||||
|
@ -791,6 +795,8 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||||
((ConfigOptionFloatOrPercent, infill_anchor))
|
((ConfigOptionFloatOrPercent, infill_anchor))
|
||||||
((ConfigOptionFloatOrPercent, infill_anchor_max))
|
((ConfigOptionFloatOrPercent, infill_anchor_max))
|
||||||
|
|
||||||
|
// Orca
|
||||||
|
((ConfigOptionBool, make_overhang_printable))
|
||||||
)
|
)
|
||||||
|
|
||||||
PRINT_CONFIG_CLASS_DEFINE(
|
PRINT_CONFIG_CLASS_DEFINE(
|
||||||
|
|
|
@ -731,7 +731,10 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||||
|| opt_key == "raft_layers"
|
|| opt_key == "raft_layers"
|
||||||
|| opt_key == "raft_contact_distance"
|
|| opt_key == "raft_contact_distance"
|
||||||
|| opt_key == "slice_closing_radius"
|
|| opt_key == "slice_closing_radius"
|
||||||
|| opt_key == "slicing_mode") {
|
|| opt_key == "slicing_mode"
|
||||||
|
|| opt_key == "make_overhang_printable"
|
||||||
|
|| opt_key == "make_overhang_printable_angle"
|
||||||
|
|| opt_key == "make_overhang_printable_hole_size") {
|
||||||
steps.emplace_back(posSlice);
|
steps.emplace_back(posSlice);
|
||||||
} else if (
|
} else if (
|
||||||
opt_key == "elefant_foot_compensation"
|
opt_key == "elefant_foot_compensation"
|
||||||
|
|
|
@ -777,6 +777,7 @@ void PrintObject::slice()
|
||||||
m_layers = new_layers(this, generate_object_layers(m_slicing_params, layer_height_profile));
|
m_layers = new_layers(this, generate_object_layers(m_slicing_params, layer_height_profile));
|
||||||
this->slice_volumes();
|
this->slice_volumes();
|
||||||
m_print->throw_if_canceled();
|
m_print->throw_if_canceled();
|
||||||
|
|
||||||
int firstLayerReplacedBy = 0;
|
int firstLayerReplacedBy = 0;
|
||||||
|
|
||||||
#if 1
|
#if 1
|
||||||
|
@ -1028,6 +1029,8 @@ void PrintObject::slice_volumes()
|
||||||
apply_mm_segmentation(*this, [print]() { print->throw_if_canceled(); });
|
apply_mm_segmentation(*this, [print]() { print->throw_if_canceled(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this->apply_conical_overhang();
|
||||||
|
m_print->throw_if_canceled();
|
||||||
|
|
||||||
BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - make_slices in parallel - begin";
|
BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - make_slices in parallel - begin";
|
||||||
{
|
{
|
||||||
|
@ -1166,6 +1169,107 @@ void PrintObject::slice_volumes()
|
||||||
BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - make_slices in parallel - end";
|
BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - make_slices in parallel - end";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PrintObject::apply_conical_overhang() {
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "Make overhang printable...";
|
||||||
|
|
||||||
|
if (m_layers.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const double conical_overhang_angle = this->config().make_overhang_printable_angle;
|
||||||
|
if (conical_overhang_angle == 90.0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const double angle_radians = conical_overhang_angle * M_PI / 180.;
|
||||||
|
const double max_hole_area = this->config().make_overhang_printable_hole_size; // in MM^2
|
||||||
|
const double tan_angle = tan(angle_radians); // the XY-component of the angle
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "angle " << angle_radians << " maxHoleArea " << max_hole_area << " tan_angle "
|
||||||
|
<< tan_angle;
|
||||||
|
const coordf_t layer_thickness = m_config.layer_height.value;
|
||||||
|
const coordf_t max_dist_from_lower_layer = tan_angle * layer_thickness; // max dist which can be bridged, in MM
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "layer_thickness " << layer_thickness << " max_dist_from_lower_layer "
|
||||||
|
<< max_dist_from_lower_layer;
|
||||||
|
|
||||||
|
// Pre-scale config
|
||||||
|
const coordf_t scaled_max_dist_from_lower_layer = -float(scale_(max_dist_from_lower_layer));
|
||||||
|
const coordf_t scaled_max_hole_area = float(scale_(scale_(max_hole_area)));
|
||||||
|
|
||||||
|
|
||||||
|
for (auto i = m_layers.rbegin() + 1; i != m_layers.rend(); ++i) {
|
||||||
|
m_print->throw_if_canceled();
|
||||||
|
Layer *layer = *i;
|
||||||
|
Layer *upper_layer = layer->upper_layer;
|
||||||
|
|
||||||
|
if (upper_layer->empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if entire layer has this disabled
|
||||||
|
if (std::all_of(layer->m_regions.begin(), layer->m_regions.end(),
|
||||||
|
[](const LayerRegion *r) { return r->slices.empty() || !r->region().config().make_overhang_printable; })) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//layer->export_region_slices_to_svg_debug("layer_before_conical_overhang");
|
||||||
|
//upper_layer->export_region_slices_to_svg_debug("upper_layer_before_conical_overhang");
|
||||||
|
|
||||||
|
|
||||||
|
// Merge the upper layer because we want to offset the entire layer uniformly, otherwise
|
||||||
|
// the model could break at the region boundary.
|
||||||
|
auto upper_poly = upper_layer->merged(float(SCALED_EPSILON));
|
||||||
|
upper_poly = union_ex(upper_poly);
|
||||||
|
|
||||||
|
// Avoid closing up of recessed holes in the base of a model.
|
||||||
|
// Detects when a hole is completely covered by the layer above and removes the hole from the layer above before
|
||||||
|
// adding it in.
|
||||||
|
// This should have no effect any time a hole in a layer interacts with any polygon in the layer above
|
||||||
|
if (scaled_max_hole_area > 0.0) {
|
||||||
|
// Merge layer for the same reason
|
||||||
|
auto current_poly = layer->merged(float(SCALED_EPSILON));
|
||||||
|
current_poly = union_ex(current_poly);
|
||||||
|
|
||||||
|
// Now go through all the holes in the current layer and check if they intersect anything in the layer above
|
||||||
|
// If not, then they're the top of a hole and should be cut from the layer above before the union
|
||||||
|
for (auto layer_polygon : current_poly) {
|
||||||
|
for (auto hole : layer_polygon.holes) {
|
||||||
|
if (std::abs(hole.area()) < scaled_max_hole_area) {
|
||||||
|
ExPolygon hole_poly(hole);
|
||||||
|
auto hole_with_above = intersection_ex(upper_poly, hole_poly);
|
||||||
|
if (!hole_with_above.empty()) {
|
||||||
|
// The hole had some intersection with the above layer, check if it's a complete overlap
|
||||||
|
auto hole_difference = xor_ex(hole_with_above, hole_poly);
|
||||||
|
if (hole_difference.empty()) {
|
||||||
|
// The layer above completely cover it, remove it from the layer above
|
||||||
|
upper_poly = diff_ex(upper_poly, hole_poly);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now offset the upper layer to be added into current layer
|
||||||
|
upper_poly = offset_ex(upper_poly, scaled_max_dist_from_lower_layer);
|
||||||
|
|
||||||
|
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) {
|
||||||
|
// export_to_svg(debug_out_path("Surface-obj-%d-layer-%d-region-%d.svg", id().id, layer->id(), region_id).c_str(),
|
||||||
|
// layer->m_regions[region_id]->slices.surfaces);
|
||||||
|
|
||||||
|
// Disable on given region
|
||||||
|
if (!upper_layer->m_regions[region_id]->region().config().make_overhang_printable) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the scaled upper poly that belongs to current region
|
||||||
|
auto p = intersection_ex(upper_layer->m_regions[region_id]->slices.surfaces, upper_poly);
|
||||||
|
// And now union it
|
||||||
|
ExPolygons layer_polygons = to_expolygons(layer->m_regions[region_id]->slices.surfaces);
|
||||||
|
layer->m_regions[region_id]->slices.set(union_ex(layer_polygons, p), stInternal);
|
||||||
|
}
|
||||||
|
//layer->export_region_slices_to_svg_debug("layer_after_conical_overhang");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//BBS: this function is used to offset contour and holes of expolygons seperately by different value
|
//BBS: this function is used to offset contour and holes of expolygons seperately by different value
|
||||||
ExPolygons PrintObject::_shrink_contour_holes(double contour_delta, double hole_delta, const ExPolygons& polys) const
|
ExPolygons PrintObject::_shrink_contour_holes(double contour_delta, double hole_delta, const ExPolygons& polys) const
|
||||||
{
|
{
|
||||||
|
|
|
@ -702,6 +702,11 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co
|
||||||
if(gcflavor == gcfKlipper)
|
if(gcflavor == gcfKlipper)
|
||||||
toggle_field("accel_to_decel_factor", config->opt_bool("accel_to_decel_enable"));
|
toggle_field("accel_to_decel_factor", config->opt_bool("accel_to_decel_enable"));
|
||||||
|
|
||||||
|
// SoftFever
|
||||||
|
bool have_make_overhang_printable = config->opt_bool("make_overhang_printable");
|
||||||
|
toggle_line("make_overhang_printable_angle", have_make_overhang_printable);
|
||||||
|
toggle_line("make_overhang_printable_hole_size", have_make_overhang_printable);
|
||||||
|
|
||||||
toggle_line("exclude_object", gcflavor == gcfKlipper);
|
toggle_line("exclude_object", gcflavor == gcfKlipper);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,7 +75,8 @@ std::map<std::string, std::vector<SimpleSettingData>> SettingsFactory::OBJECT_C
|
||||||
//{"initial_layer_print_height", "",2},
|
//{"initial_layer_print_height", "",2},
|
||||||
{"seam_position", "",2},
|
{"seam_position", "",2},
|
||||||
{"slice_closing_radius", "",3}, {"resolution", "",4},
|
{"slice_closing_radius", "",3}, {"resolution", "",4},
|
||||||
{"xy_hole_compensation", "",5}, {"xy_contour_compensation", "",6}, {"elefant_foot_compensation", "",7}
|
{"xy_hole_compensation", "",5}, {"xy_contour_compensation", "",6}, {"elefant_foot_compensation", "",7},
|
||||||
|
{"make_overhang_printable_angle","", 8},{"make_overhang_printable_hole_size","",9}
|
||||||
}},
|
}},
|
||||||
{ L("Support"), {{"brim_type", "",1},{"brim_width", "",2},{"brim_object_gap", "",3},
|
{ L("Support"), {{"brim_type", "",1},{"brim_width", "",2},{"brim_object_gap", "",3},
|
||||||
{"enable_support", "",4},{"support_type", "",5},{"support_threshold_angle", "",6},{"support_on_build_plate_only", "",7},
|
{"enable_support", "",4},{"support_type", "",5},{"support_threshold_angle", "",6},{"support_on_build_plate_only", "",7},
|
||||||
|
@ -91,7 +92,7 @@ std::map<std::string, std::vector<SimpleSettingData>> SettingsFactory::OBJECT_C
|
||||||
|
|
||||||
std::map<std::string, std::vector<SimpleSettingData>> SettingsFactory::PART_CATEGORY_SETTINGS=
|
std::map<std::string, std::vector<SimpleSettingData>> SettingsFactory::PART_CATEGORY_SETTINGS=
|
||||||
{
|
{
|
||||||
{ L("Quality"), {{"ironing_type", "",8},{"ironing_flow", "",9},{"ironing_spacing", "",10},{"bridge_flow", "",11},{"bridge_density", "", 1}
|
{ L("Quality"), {{"ironing_type", "",8},{"ironing_flow", "",9},{"ironing_spacing", "",10},{"bridge_flow", "",11},{"make_overhang_printable", "",11},{"bridge_density", "", 1}
|
||||||
}},
|
}},
|
||||||
{ L("Strength"), {{"wall_loops", "",1},{"top_shell_layers", L("Top Solid Layers"),1},{"top_shell_thickness", L("Top Minimum Shell Thickness"),1},
|
{ L("Strength"), {{"wall_loops", "",1},{"top_shell_layers", L("Top Solid Layers"),1},{"top_shell_thickness", L("Top Minimum Shell Thickness"),1},
|
||||||
{"bottom_shell_layers", L("Bottom Solid Layers"),1}, {"bottom_shell_thickness", L("Bottom Minimum Shell Thickness"),1},
|
{"bottom_shell_layers", L("Bottom Solid Layers"),1}, {"bottom_shell_thickness", L("Bottom Minimum Shell Thickness"),1},
|
||||||
|
|
|
@ -1888,6 +1888,9 @@ void TabPrint::build()
|
||||||
optgroup->append_single_option_line("only_one_wall_top");
|
optgroup->append_single_option_line("only_one_wall_top");
|
||||||
optgroup->append_single_option_line("only_one_wall_first_layer");
|
optgroup->append_single_option_line("only_one_wall_first_layer");
|
||||||
optgroup->append_single_option_line("detect_overhang_wall");
|
optgroup->append_single_option_line("detect_overhang_wall");
|
||||||
|
optgroup->append_single_option_line("make_overhang_printable");
|
||||||
|
optgroup->append_single_option_line("make_overhang_printable_angle");
|
||||||
|
optgroup->append_single_option_line("make_overhang_printable_hole_size");
|
||||||
optgroup->append_single_option_line("reduce_crossing_wall");
|
optgroup->append_single_option_line("reduce_crossing_wall");
|
||||||
optgroup->append_single_option_line("max_travel_detour_distance");
|
optgroup->append_single_option_line("max_travel_detour_distance");
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue