Add top/bottom surface fill order control (Outward/Inward) for Concentric, Archimedean Chords and Octagram Spiral (#14179)

Co-authored-by: Ian Bassi <ian.bassi@outlook.com>
This commit is contained in:
Mqrius
2026-07-14 20:34:13 +02:00
committed by GitHub
parent 50768e0716
commit c84d5c7943
15 changed files with 210 additions and 89 deletions

View File

@@ -272,10 +272,12 @@ struct SurfaceFillParams
// For Gyroid: when true, use the parameterized "optimized" wave.
bool gyroid_optimized = false;
bool anisotropic_surfaces{false};
CenterOfSurfacePattern center_of_surface_pattern{CenterOfSurfacePattern::Each_Surface};
bool separated_infills{false};
// Orca: forced print order of surface fill loops/fragments for center-based patterns.
SurfaceFillOrder fill_order = SurfaceFillOrder::Default;
bool operator<(const SurfaceFillParams &rhs) const {
#define RETURN_COMPARE_NON_EQUAL(KEY) if (this->KEY < rhs.KEY) return true; if (this->KEY > rhs.KEY) return false;
#define RETURN_COMPARE_NON_EQUAL_TYPED(TYPE, KEY) if (TYPE(this->KEY) < TYPE(rhs.KEY)) return true; if (TYPE(this->KEY) > TYPE(rhs.KEY)) return false;
@@ -308,9 +310,9 @@ struct SurfaceFillParams
RETURN_COMPARE_NON_EQUAL(skin_infill_depth);
RETURN_COMPARE_NON_EQUAL(infill_overhang_angle);
RETURN_COMPARE_NON_EQUAL(gyroid_optimized);
RETURN_COMPARE_NON_EQUAL(anisotropic_surfaces);
RETURN_COMPARE_NON_EQUAL(center_of_surface_pattern);
RETURN_COMPARE_NON_EQUAL(separated_infills);
RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, fill_order);
return false;
}
@@ -337,10 +339,10 @@ struct SurfaceFillParams
this->infill_lock_depth == rhs.infill_lock_depth &&
this->skin_infill_depth == rhs.skin_infill_depth &&
this->infill_overhang_angle == rhs.infill_overhang_angle &&
this->anisotropic_surfaces == rhs.anisotropic_surfaces &&
this->center_of_surface_pattern == rhs.center_of_surface_pattern &&
this->separated_infills == rhs.separated_infills &&
this->gyroid_optimized == rhs.gyroid_optimized;
this->gyroid_optimized == rhs.gyroid_optimized &&
this->fill_order == rhs.fill_order;
}
};
@@ -879,7 +881,6 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
params.lateral_lattice_angle_1 = region_config.lateral_lattice_angle_1;
params.lateral_lattice_angle_2 = region_config.lateral_lattice_angle_2;
params.infill_overhang_angle = region_config.infill_overhang_angle;
params.anisotropic_surfaces = region_config.anisotropic_surfaces;
params.center_of_surface_pattern = region_config.center_of_surface_pattern;
params.separated_infills = region_config.separated_infills;
if (params.pattern == ipLockedZag) {
@@ -936,6 +937,14 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
params.extruder = region_config.bottom_surface_filament_id;
else if (params.extrusion_role == erSolidInfill)
params.extruder = region_config.internal_solid_filament_id;
// Orca: forced fill order applies only to top/bottom surfaces filled with a
// center-based pattern; everything else stays at Default to keep batching together.
if (params.pattern == ipConcentric || params.pattern == ipArchimedeanChords || params.pattern == ipOctagramSpiral) {
if (params.extrusion_role == erTopSolidInfill)
params.fill_order = region_config.top_surface_fill_order.value;
else if (params.extrusion_role == erBottomSurface)
params.fill_order = region_config.bottom_surface_fill_order.value;
}
// Orca: apply fill multiline only for sparse infill
params.multiline = params.extrusion_role == erInternalInfill ? int(region_config.fill_multiline) : 1;
@@ -1322,12 +1331,12 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
auto &region_config = layerm->region().config();
params.config = &region_config;
params.pattern = surface_fill.params.pattern;
params.fill_order = surface_fill.params.fill_order;
// Orca: Checking the filling of a centered surface by drawing for each model parts
bool is_top_or_bottom = params.extrusion_role == erTopSolidInfill || params.extrusion_role == erBottomSurface;
bool is_centered_infill = surface_fill.params.pattern == ipArchimedeanChords || surface_fill.params.pattern == ipOctagramSpiral;
if (is_top_or_bottom) {
params.is_anisotropic = surface_fill.params.anisotropic_surfaces; // Orca: anisotropic surfaces
params.center_of_surface_pattern = surface_fill.params.center_of_surface_pattern; // Orca: center of surface pattern
}
// Orca: Each_Model centers the pattern on each model part's bbox; Each_Surface / Each_Assembly

View File

@@ -162,10 +162,11 @@ void Fill::fill_surface_extrusion(const Surface* surface, const FillParams& para
out.push_back(eec = new ExtrusionEntityCollection());
// Only concentric fills are not sorted.
eec->no_sort = this->no_sort();
// ORCA: special flag for flow rate calibration
auto is_flow_calib = params.extrusion_role == erTopSolidInfill && this->print_object_config->has("calib_flowrate_topinfill_special_order") &&
this->print_object_config->option("calib_flowrate_topinfill_special_order")->getBool();
if (is_flow_calib || params.is_anisotropic) { // Orca: disable sorting while anisotropic surfaces
// Orca: a forced surface fill order must survive the G-code path planner, which would
// otherwise re-chain and possibly reverse the paths. This also covers the flow rate
// calibration, which forces an outward fill order on its top surfaces.
const bool keep_fill_order = params.fill_order != SurfaceFillOrder::Default;
if (keep_fill_order) {
eec->no_sort = true;
}
size_t idx = eec->entities.size();
@@ -180,14 +181,13 @@ void Fill::fill_surface_extrusion(const Surface* surface, const FillParams& para
params.extrusion_role,
flow_mm3_per_mm, float(flow_width), params.flow.height());
}
if (!params.can_reverse || is_flow_calib) {
if (!params.can_reverse || keep_fill_order) {
for (size_t i = idx; i < eec->entities.size(); i++)
eec->entities[i]->set_reverse();
}
// Orca: run gap fill
if (!(params.is_anisotropic)) // Orca: Disable gap filling while anisotropic
this->_create_gap_fill(surface, params, eec);
this->_create_gap_fill(surface, params, eec);
}
}

View File

@@ -100,13 +100,16 @@ struct FillParams
bool dont_sort{ false }; // do not sort the lines, just simply connect them
bool can_reverse{true};
// Orca: forced print order of surface fill loops/fragments for center-based patterns
// (Concentric, Archimedean Chords, Octagram Spiral). Default keeps shortest-path ordering.
SurfaceFillOrder fill_order { SurfaceFillOrder::Default };
float horiz_move{0.0}; //move infill to get cross zag pattern
bool symmetric_infill_y_axis{false};
coord_t symmetric_y_axis{0};
bool locked_zag{false};
float infill_lock_depth{0.0};
float skin_infill_depth{0.0};
bool is_anisotropic{false};
CenterOfSurfacePattern center_of_surface_pattern{CenterOfSurfacePattern::Each_Surface};
};
static_assert(IsTriviallyCopyable<FillParams>::value, "FillParams class is not POD (and it should be - see constructor).");

View File

@@ -41,6 +41,10 @@ void FillConcentric::_fill_surface_single(
// generate paths from the outermost to the innermost, to avoid
// adhesion problems of the first central tiny loops
loops = union_pt_chained_outside_in(loops);
// Orca: an outward fill order prints the innermost loops first instead.
if (params.fill_order == SurfaceFillOrder::Outward)
std::reverse(loops.begin(), loops.end());
// split paths using a nearest neighbor search
size_t iPathFirst = polylines_out.size();
@@ -108,6 +112,17 @@ void FillConcentric::_fill_surface_single(const FillParams& params,
all_extrusions.emplace_back(&wall);
}
// Orca: a forced fill order prints the loops in strictly monotonic depth order so
// that surfaces broken up by holes or slots cannot hop outward and back inward.
const bool forced_fill_order = params.fill_order != SurfaceFillOrder::Default;
if (forced_fill_order) {
const bool outward = params.fill_order == SurfaceFillOrder::Outward;
std::stable_sort(all_extrusions.begin(), all_extrusions.end(),
[outward](const Arachne::ExtrusionLine *a, const Arachne::ExtrusionLine *b) {
return outward ? a->inset_idx > b->inset_idx : a->inset_idx < b->inset_idx;
});
}
// Split paths using a nearest neighbor search.
size_t firts_poly_idx = thick_polylines_out.size();
Point last_pos(0, 0);
@@ -136,7 +151,8 @@ void FillConcentric::_fill_surface_single(const FillParams& params,
if (j < thick_polylines_out.size())
thick_polylines_out.erase(thick_polylines_out.begin() + int(j), thick_polylines_out.end());
reorder_by_shortest_traverse(thick_polylines_out);
if (!forced_fill_order)
reorder_by_shortest_traverse(thick_polylines_out);
}
else {
Polylines polylines;

View File

@@ -133,49 +133,26 @@ void FillPlanePath::_fill_surface_single(
polylines = intersection_pl(std::move(polylines), expolygon);
if (!polylines.empty()) {
Polylines chained;
if (!params.is_anisotropic) { // Orca: not anisotropic surface
if ((params.dont_connect() || params.density > 0.5)) {
// ORCA: special flag for flow rate calibration
auto is_flow_calib = params.extrusion_role == erTopSolidInfill &&
this->print_object_config->has("calib_flowrate_topinfill_special_order") &&
this->print_object_config->option("calib_flowrate_topinfill_special_order")->getBool() &&
dynamic_cast<FillArchimedeanChords*>(this);
if (is_flow_calib) {
// We want the spiral part to be printed inside-out
// Find the center spiral line first, by looking for the longest one
auto it = std::max_element(polylines.begin(), polylines.end(),
[](const Polyline& a, const Polyline& b) { return a.length() < b.length(); });
Polyline center_spiral = std::move(*it);
// Ensure the spiral is printed from inside to out
if ((center_spiral.first_point().squaredNorm() > center_spiral.last_point().squaredNorm())) {
center_spiral.reverse();
}
// Chain the other polylines
polylines.erase(it);
chained = chain_polylines(std::move(polylines), nullptr);
// Then add the center spiral back
chained.push_back(std::move(center_spiral));
} else {
chained = chain_polylines(std::move(polylines), nullptr);
if (params.dont_connect() || params.density > 0.5) {
if (params.fill_order != SurfaceFillOrder::Default) {
// Orca: print the fragments in the order they appear along the generated
// path, which runs from the center outwards. The Euclidean distance from
// the center cannot be used for this: along the Octagram Spiral the radius
// oscillates by far more than the ring spacing, so fragments of different
// rings would interleave.
restore_source_path_order(polyline, polylines);
chained = std::move(polylines);
if (params.fill_order == SurfaceFillOrder::Inward) {
// The source path runs from the center outwards; flip everything for inward.
std::reverse(chained.begin(), chained.end());
for (Polyline &pl : chained)
pl.reverse();
}
} else
connect_infill(std::move(polylines), expolygon, chained, this->spacing, params);
} else { // Orca: anisotropic surface
const Point _center(0., 0.);
for (Polyline& segment : polylines) { // sort paths by its direction
if (segment.size() > 1) { // need at least two points to evaluate direction
if (segment.first_point().ccw(segment.points[1], _center) < 0)
segment.reverse();
}
chained.emplace_back(std::move(segment));
} else {
chained = chain_polylines(std::move(polylines), nullptr);
}
std::sort(chained.begin(), chained.end(), [&_center](const Polyline& a, const Polyline& b) { // just sort polylines from center to outside
return a.distance_to(_center) < b.distance_to(_center);
});
}
} else
connect_infill(std::move(polylines), expolygon, chained, this->spacing, params);
// paths must be repositioned and rotated back
for (Polyline& pl : chained) {
pl.translate(shift.x(), shift.y());