mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-15 15:03:49 +00:00
Top Surface Expansion (#14296)
Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
This commit is contained in:
@@ -188,6 +188,18 @@ ExPolygons offset_ex_2(const ExPolygons &expolygons, double delta)
|
||||
return results;
|
||||
}
|
||||
|
||||
ExPolygons offset_ex_2(const ExPolygons &expolygons, double delta, Clipper2Lib::JoinType joinType)
|
||||
{
|
||||
Clipper2Lib::Paths64 subject = Slic3rExPolygons_to_Paths64(expolygons);
|
||||
Clipper2Lib::ClipperOffset offsetter;
|
||||
offsetter.AddPaths(subject, joinType, Clipper2Lib::EndType::Polygon);
|
||||
Clipper2Lib::PolyPath64 polytree;
|
||||
offsetter.Execute(delta, polytree);
|
||||
ExPolygons results = PolyTreeToExPolygons(std::move(polytree));
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
ExPolygons offset2_ex_2(const ExPolygons& expolygons, double delta1, double delta2)
|
||||
{
|
||||
// 1st offset
|
||||
|
||||
@@ -15,6 +15,7 @@ Slic3r::Polylines diff_pl_2(const Slic3r::Polylines& subject, const Slic3r::Pol
|
||||
ExPolygons union_ex_2(const Polygons &expolygons);
|
||||
ExPolygons union_ex_2(const ExPolygons &expolygons);
|
||||
ExPolygons offset_ex_2(const ExPolygons &expolygons, double delta);
|
||||
ExPolygons offset_ex_2(const ExPolygons &expolygons, double delta, Clipper2Lib::JoinType joinType);
|
||||
ExPolygons offset2_ex_2(const ExPolygons &expolygons, double delta1, double delta2);
|
||||
}
|
||||
|
||||
|
||||
@@ -571,6 +571,19 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
|
||||
return extrusion_coll;
|
||||
}
|
||||
|
||||
// ORCA: only_one_wall_top detects the top as "slice − upper", so a feature rising from the middle of a
|
||||
// top surface becomes an enclosed hole that gets ringed with extra inner walls. Fill those holes back
|
||||
// into the top. Only holes that are both covered by the upper layer (excludes bridges) and backed by
|
||||
// solid material (excludes voids) are filled.
|
||||
static ExPolygons fill_enclosed_top_feature_holes(const ExPolygons &top, const Polygons &covered_by_upper, const ExPolygons &solid)
|
||||
{
|
||||
ExPolygons filled = top;
|
||||
for (ExPolygon &ex : filled)
|
||||
ex.holes.clear();
|
||||
const ExPolygons feature_holes = intersection_ex(intersection_ex(diff_ex(filled, top), covered_by_upper), solid);
|
||||
return feature_holes.empty() ? top : union_ex(top, feature_holes);
|
||||
}
|
||||
|
||||
void PerimeterGenerator::split_top_surfaces(const ExPolygons &orig_polygons, ExPolygons &top_fills,
|
||||
ExPolygons &non_top_polygons, ExPolygons &fill_clip) const {
|
||||
// other perimeters
|
||||
@@ -636,6 +649,8 @@ void PerimeterGenerator::split_top_surfaces(const ExPolygons &orig_polygons, ExP
|
||||
ExPolygons delete_bridge = diff_ex(orig_polygons, bridge_checker, ApplySafetyOffset::Yes);
|
||||
|
||||
ExPolygons top_polygons = diff_ex(delete_bridge, upper_polygons_series_clipped, ApplySafetyOffset::Yes);
|
||||
top_polygons = fill_enclosed_top_feature_holes(top_polygons, upper_polygons_series_clipped, orig_polygons);
|
||||
|
||||
// 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);
|
||||
@@ -2194,6 +2209,7 @@ void PerimeterGenerator::process_arachne()
|
||||
upper_slices_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*upper_slices, infill_contour_bbox);
|
||||
|
||||
top_expolygons = diff_ex(infill_contour, upper_slices_clipped);
|
||||
top_expolygons = fill_enclosed_top_feature_holes(top_expolygons, upper_slices_clipped, infill_contour);
|
||||
|
||||
if (!top_expolygons.empty()) {
|
||||
if (lower_slices != nullptr) {
|
||||
|
||||
@@ -1044,6 +1044,9 @@ static std::vector<std::string> s_Preset_print_options{
|
||||
"lightning_prune_angle",
|
||||
"lightning_straightening_angle",
|
||||
"top_surface_pattern",
|
||||
"top_surface_expansion",
|
||||
"top_surface_expansion_margin",
|
||||
"top_surface_expansion_direction",
|
||||
"bottom_surface_pattern",
|
||||
"infill_direction",
|
||||
"solid_infill_direction",
|
||||
|
||||
@@ -221,6 +221,13 @@ static t_config_enum_values s_keys_map_FuzzySkinMode {
|
||||
};
|
||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(FuzzySkinMode)
|
||||
|
||||
static t_config_enum_values s_keys_map_TopSurfaceExpansionDirection {
|
||||
{ "inward_and_outward", int(TopSurfaceExpansionDirection::InwardAndOutward) },
|
||||
{ "inward", int(TopSurfaceExpansionDirection::Inward) },
|
||||
{ "outward", int(TopSurfaceExpansionDirection::Outward) }
|
||||
};
|
||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(TopSurfaceExpansionDirection)
|
||||
|
||||
static t_config_enum_values s_keys_map_InfillPattern {
|
||||
{ "monotonic", ipMonotonic },
|
||||
{ "monotonicline", ipMonotonicLine },
|
||||
@@ -2123,6 +2130,47 @@ void PrintConfigDef::init_fff_params()
|
||||
def->max = 100;
|
||||
def->set_default_value(new ConfigOptionPercent(100));
|
||||
|
||||
def = this->add("top_surface_expansion", coFloat);
|
||||
def->label = L("Top surface expansion");
|
||||
def->category = L("Strength");
|
||||
def->tooltip = L("Expands the top surfaces by this distance to connect distinct top surfaces and fill gaps.\n"
|
||||
"Useful for cases where the top surface is interrupted by a raised feature, such as text on a plane."
|
||||
"Expanding it removes the holes beneath these features and creates a continuous path with a better finish for printing on top."
|
||||
"The expansion is applied to the original top surface, before any other processing such as bridging or overhang detection.");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(0));
|
||||
|
||||
def = this->add("top_surface_expansion_margin", coFloat);
|
||||
def->label = L("Top expansion wall margin");
|
||||
def->category = L("Strength");
|
||||
def->tooltip = L("Using “Top surface expansion” may cause a surface that did not previously touch the model's outer walls to now do so.\n"
|
||||
"This can cause contraction marks (such as the hull line) on the outer walls.\n"
|
||||
"By adding a small margin, this contraction will not occur directly on the walls, thereby preventing a visible mark.");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->max = 10;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(0));
|
||||
|
||||
def = this->add("top_surface_expansion_direction", coEnum);
|
||||
def->label = L("Top expansion direction");
|
||||
def->category = L("Strength");
|
||||
def->tooltip = L("Direction in which the top surface expansion grows.\n"
|
||||
" - Inward grows into the holes and gaps left by features rising from the middle of a top surface.\n"
|
||||
" - Outward grows the outer edge of the surface, connecting surfaces separated by features that can divide a surface, such as a lattice pattern.\n"
|
||||
" - Inward and Outward does both.");
|
||||
def->enum_keys_map = &ConfigOptionEnum<TopSurfaceExpansionDirection>::get_enum_values();
|
||||
def->enum_values.push_back("inward_and_outward");
|
||||
def->enum_values.push_back("inward");
|
||||
def->enum_values.push_back("outward");
|
||||
def->enum_labels.push_back(L("Inward and Outward"));
|
||||
def->enum_labels.push_back(L("Inward"));
|
||||
def->enum_labels.push_back(L("Outward"));
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionEnum<TopSurfaceExpansionDirection>(TopSurfaceExpansionDirection::InwardAndOutward));
|
||||
|
||||
def = this->add("bottom_surface_pattern", coEnum);
|
||||
def->label = L("Bottom surface pattern");
|
||||
def->category = L("Strength");
|
||||
|
||||
@@ -62,6 +62,13 @@ enum class FuzzySkinMode {
|
||||
Combined,
|
||||
};
|
||||
|
||||
// ORCA: direction in which top_surface_expansion grows the top surfaces.
|
||||
enum class TopSurfaceExpansionDirection {
|
||||
InwardAndOutward,
|
||||
Inward,
|
||||
Outward,
|
||||
};
|
||||
|
||||
enum class NoiseType {
|
||||
Classic,
|
||||
Perlin,
|
||||
@@ -540,6 +547,7 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PrinterTechnology)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeFlavor)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(FuzzySkinType)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(FuzzySkinMode)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(TopSurfaceExpansionDirection)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(WipeTowerType)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(NoiseType)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(InfillPattern)
|
||||
@@ -1194,6 +1202,9 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionFloatOrPercent, top_surface_line_width))
|
||||
((ConfigOptionInt, top_shell_layers))
|
||||
((ConfigOptionFloat, top_shell_thickness))
|
||||
((ConfigOptionFloat, top_surface_expansion))
|
||||
((ConfigOptionFloat, top_surface_expansion_margin))
|
||||
((ConfigOptionEnum<TopSurfaceExpansionDirection>, top_surface_expansion_direction))
|
||||
((ConfigOptionFloatsNullable, top_surface_speed))
|
||||
//BBS
|
||||
((ConfigOptionBoolsNullable, enable_overhang_speed))
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "Print.hpp"
|
||||
#include "BoundingBox.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Clipper2Utils.hpp"
|
||||
#include "ElephantFootCompensation.hpp"
|
||||
#include "Geometry.hpp"
|
||||
#include "I18N.hpp"
|
||||
@@ -1297,6 +1298,9 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "infill_combination_max_layer_height"
|
||||
|| opt_key == "bottom_shell_thickness"
|
||||
|| opt_key == "top_shell_thickness"
|
||||
|| opt_key == "top_surface_expansion"
|
||||
|| opt_key == "top_surface_expansion_margin"
|
||||
|| opt_key == "top_surface_expansion_direction"
|
||||
|| opt_key == "minimum_sparse_infill_area"
|
||||
|| opt_key == "sparse_infill_filament_id"
|
||||
|| opt_key == "internal_solid_filament_id"
|
||||
@@ -1686,6 +1690,69 @@ void PrintObject::detect_surfaces_type()
|
||||
}
|
||||
}
|
||||
|
||||
// ORCA: Expand the top surfaces outward by top_surface_expansion in every direction. This
|
||||
// enlarges the top solid infill and, in particular, grows it over the covered material left
|
||||
// by features rising from the middle of a top surface (filling holes and joining tops so the
|
||||
// features rest on it). The expansion stays inside the section it belongs to: each connected
|
||||
// solid island has its own outer wall, so the top is grown within each island separately and
|
||||
// clipped to it - growing one island's top across the gap into another island (which may have
|
||||
// no top surface, leaving a partially filled layer) is never allowed. The top infill sits
|
||||
// inside the perimeters, so the margin is measured from the walls: the island is inset by the
|
||||
// band the walls consume (outer wall + inner walls) plus the configured margin, making that
|
||||
// value the real clearance between the expanded top and the walls (avoiding a hull line). The
|
||||
// original top is unioned back in, so where it already sits within that band it is kept as-is.
|
||||
// Never claims a bottom surface.
|
||||
const double top_expansion = layerm->region().config().top_surface_expansion.value;
|
||||
if (top_expansion > 0. && ! top.empty()) {
|
||||
const double d = scale_(top_expansion);
|
||||
const auto jt = Clipper2Lib::JoinType::Miter;
|
||||
const ExPolygons T = union_ex(to_expolygons(top));
|
||||
const int wall_loops = layerm->region().config().wall_loops.value;
|
||||
const double wall_band = wall_loops <= 0 ? 0. :
|
||||
double(layerm->flow(frExternalPerimeter).scaled_width()) +
|
||||
double(layerm->flow(frPerimeter).scaled_width()) * double(wall_loops - 1);
|
||||
const double margin = scale_(layerm->region().config().top_surface_expansion_margin.value);
|
||||
// minimum real top to act on: ignore anything thinner than ~2 top-infill lines
|
||||
const float min_top = float(layerm->flow(frTopSolidInfill).scaled_width());
|
||||
const auto direction = layerm->region().config().top_surface_expansion_direction.value;
|
||||
|
||||
ExPolygons grown;
|
||||
for (const ExPolygon &island : union_ex(layerm_slices_surfaces)) {
|
||||
// The top infill only exists inside the perimeters, so seed and measure from the infill
|
||||
// region (the island minus the wall band), not the raw slice. A section whose only
|
||||
// exposed top lies in the wall band - i.e. a layer where the top is just the walls
|
||||
// themselves - has no infill here and is skipped, instead of being flooded inward by
|
||||
// the expansion. Thin slivers inside the infill region are dropped by the opening too.
|
||||
const ExPolygons infill_region = wall_band > 0. ? offset_ex(island, -float(wall_band)) : ExPolygons{ island };
|
||||
const ExPolygons island_top = intersection_ex(T, infill_region);
|
||||
if (opening_ex(island_top, min_top).empty())
|
||||
continue; // no real top infill in this section - never expand into it
|
||||
|
||||
// grow by d, then keep only the part allowed by the configured direction: inward fills
|
||||
// the holes/gaps left by features (clip the growth back to the top's own filled outline,
|
||||
// which leaves the outer edge fixed), outward grows the outer edge toward the walls (drop
|
||||
// the growth that fell into the original holes), and inward+outward keeps both.
|
||||
ExPolygons expanded = offset_ex_2(island_top, d, jt);
|
||||
if (direction != TopSurfaceExpansionDirection::InwardAndOutward) {
|
||||
ExPolygons outline; // the top with its holes filled (same outer edge)
|
||||
outline.reserve(island_top.size());
|
||||
for (const ExPolygon &ex : island_top)
|
||||
outline.emplace_back(ex.contour);
|
||||
outline = union_ex(outline);
|
||||
expanded = direction == TopSurfaceExpansionDirection::Inward ?
|
||||
intersection_ex(expanded, outline) : // only growth into the holes
|
||||
diff_ex(expanded, diff_ex(outline, island_top)); // only growth past the outer edge
|
||||
}
|
||||
// hold the expansion clear of the walls by the configured margin
|
||||
const ExPolygons allowed = margin > 0. ? offset_ex(infill_region, -float(margin)) : infill_region;
|
||||
append(grown, intersection_ex(expanded, allowed));
|
||||
}
|
||||
|
||||
ExPolygons new_top = diff_ex(union_ex(T, grown), to_expolygons(bottom));
|
||||
top.clear();
|
||||
surfaces_append(top, std::move(new_top), stTop);
|
||||
}
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
{
|
||||
static int iRun = 0;
|
||||
|
||||
@@ -713,6 +713,13 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
toggle_field("top_layer_direction", has_top_shell);
|
||||
toggle_field("bottom_layer_direction", has_bottom_shell);
|
||||
|
||||
toggle_line("top_surface_expansion", has_top_shell);
|
||||
toggle_line("top_surface_expansion_margin", has_top_shell);
|
||||
bool has_top_surface_expansion = config->opt_float("top_surface_expansion") > 0;
|
||||
toggle_field("top_surface_expansion_margin", has_top_surface_expansion);
|
||||
toggle_line("top_surface_expansion_direction", has_top_shell);
|
||||
toggle_field("top_surface_expansion_direction", has_top_surface_expansion);
|
||||
|
||||
for (auto el : { "infill_direction", "sparse_infill_line_width", "gap_fill_target","filter_out_gap_fill","infill_wall_overlap",
|
||||
"bridge_angle", "internal_bridge_angle", "relative_bridge_angle",
|
||||
"solid_infill_direction", "solid_infill_rotate_template", "internal_solid_infill_pattern", "internal_solid_filament_id", "top_surface_filament_id", "bottom_surface_filament_id",
|
||||
|
||||
@@ -132,6 +132,9 @@ std::map<std::string, std::vector<SimpleSettingData>> SettingsFactory::PART_CATE
|
||||
{"infill_anchor", "", 1},
|
||||
{"infill_anchor_max", "", 1},
|
||||
{"top_surface_pattern", "", 1},
|
||||
{"top_surface_expansion", "", 1},
|
||||
{"top_surface_expansion_margin", "", 1},
|
||||
{"top_surface_expansion_direction", "", 1},
|
||||
{"bottom_surface_pattern", "", 1},
|
||||
{"internal_solid_infill_pattern", "", 1},
|
||||
{"align_infill_direction_to_model", "", 1},
|
||||
@@ -187,7 +190,7 @@ std::vector<SimpleSettingData> SettingsFactory::get_visible_options(const std::s
|
||||
//Quality
|
||||
"wall_infill_order", "ironing_type", "inner_wall_line_width", "outer_wall_line_width", "top_surface_line_width",
|
||||
//Shell
|
||||
"wall_loops", "top_shell_layers", "bottom_shell_layers", "top_shell_thickness", "bottom_shell_thickness",
|
||||
"wall_loops", "top_shell_layers", "bottom_shell_layers", "top_shell_thickness", "bottom_shell_thickness", "top_surface_expansion", "top_surface_expansion_margin", "top_surface_expansion_direction",
|
||||
//Infill
|
||||
"sparse_infill_density", "sparse_infill_pattern", "top_surface_pattern", "bottom_surface_pattern", "infill_combination", "infill_direction", "infill_wall_overlap",
|
||||
//speed
|
||||
|
||||
@@ -2743,6 +2743,10 @@ void TabPrint::build()
|
||||
optgroup->append_single_option_line("top_surface_density", "strength_settings_top_bottom_shells#surface-density");
|
||||
optgroup->append_single_option_line("top_surface_pattern", "strength_settings_top_bottom_shells#surface-pattern");
|
||||
optgroup->append_single_option_line("top_layer_direction", "strength_settings_infill#top-direction");
|
||||
optgroup->append_single_option_line("top_surface_hole_contraction", "strength_settings_top_bottom_shells#surface-hole-contraction");
|
||||
optgroup->append_single_option_line("top_surface_expansion", "strength_settings_top_bottom_shells#surface-expansion");
|
||||
optgroup->append_single_option_line("top_surface_expansion_margin", "strength_settings_top_bottom_shells#surface-expansion-margin");
|
||||
optgroup->append_single_option_line("top_surface_expansion_direction", "strength_settings_top_bottom_shells#surface-expansion-direction");
|
||||
optgroup->append_single_option_line("bottom_shell_layers", "strength_settings_top_bottom_shells#shell-layers");
|
||||
optgroup->append_single_option_line("bottom_shell_thickness", "strength_settings_top_bottom_shells#shell-thickness");
|
||||
optgroup->append_single_option_line("bottom_surface_density", "strength_settings_top_bottom_shells#surface-density");
|
||||
|
||||
Reference in New Issue
Block a user