mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-18 22:42:37 +00:00
Merge Main into Belt Printer
Merge origin/main (00429da739) into belt-printer.
Conflicts resolved:
- src/CMakeLists.txt: keep both wxInspector workarounds.
- GCodeProcessor.cpp: keep the belt compare_pos / z_for_height lines.
- PrintObjectSlice.cpp: the belt bbox-Z guard also covers main's
printable_region_ids bookkeeping.
- TreeSupport.cpp: the belt-floor check runs before main's PendingNode
queueing.
- Tab.hpp: keep the belt fields, drop the removed upload description
fields.
- tests/libslic3r/CMakeLists.txt: keep both test files.
Also included:
- eSUN PLA belt presets declare their own filament_id (OFkrxQC4) and
scripts/filament_id_snapshot.json is regenerated, as main's filament_id
check requires.
- Custom.json version bumped to 02.04.00.05 so the belt entries reach
existing installs.
- Fix the ambiguous WithinRel call in the belt apron width test, which
otherwise breaks the fff_print build.
This commit is contained in:
+193
-127
@@ -25,9 +25,11 @@
|
||||
#include "TriangleMeshSlicer.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "Fill/FillAdaptive.hpp"
|
||||
#include "Fill/Fill.hpp"
|
||||
#include "Fill/FillLightning.hpp"
|
||||
#include "Format/STL.hpp"
|
||||
#include "format.hpp"
|
||||
#include "AABBTreeIndirect.hpp"
|
||||
#include "AABBTreeLines.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
@@ -681,6 +683,98 @@ void PrintObject::prepare_infill()
|
||||
} // for each region
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
|
||||
// Orca: precompute the object's 3D connected bodies for separated infills / per-model
|
||||
// centering. Two islands belong to the same body when their slices overlap on adjacent
|
||||
// layers; islands that only overlap in top-down projection but never touch (e.g. interleaved
|
||||
// chain links) stay separate, matching "split to objects". Each layer island then records
|
||||
// the full bounding box of its body, so its infill is centered on that body as if it were
|
||||
// sliced alone. Compute this before bridges so anchors and extrusion share the same origin.
|
||||
bool needs_separated_components = false;
|
||||
for (size_t i = 0; i < this->num_printing_regions(); ++ i) {
|
||||
const PrintRegionConfig &rc = this->printing_region(i).config();
|
||||
if (rc.separated_infills || rc.center_of_surface_pattern == CenterOfSurfacePattern::Each_Model) {
|
||||
needs_separated_components = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Orca: Fast path: the feature only changes anything when the object is made of more than one
|
||||
// connected body. Detect that cheaply the same way as "Split to objects" — more than one
|
||||
// model part, or a single part whose mesh is splittable (is_splittable() is cached). A single
|
||||
// body already shares the object center, i.e. the default, so skip the connectivity pass.
|
||||
if (needs_separated_components) {
|
||||
int parts = 0;
|
||||
const ModelVolume *first_part = nullptr;
|
||||
for (const ModelVolume *v : this->model_object()->volumes)
|
||||
if (v->is_model_part()) { ++ parts; first_part = v; }
|
||||
if (parts <= 1 && ! (first_part != nullptr && first_part->is_splittable()))
|
||||
needs_separated_components = false;
|
||||
}
|
||||
for (Layer *layer : m_layers)
|
||||
layer->lslices_separated_component_bboxes.clear();
|
||||
if (needs_separated_components) {
|
||||
const size_t nl = m_layers.size();
|
||||
std::vector<size_t> offset(nl + 1, 0); // Orca: flat index of the first island of each layer
|
||||
for (size_t i = 0; i < nl; ++ i)
|
||||
offset[i + 1] = offset[i] + m_layers[i]->lslices.size();
|
||||
const size_t nreg = offset[nl];
|
||||
// Orca: Union-find over every (layer, island).
|
||||
std::vector<size_t> parent(nreg);
|
||||
for (size_t i = 0; i < nreg; ++ i) parent[i] = i;
|
||||
auto find = [&parent](size_t x) {
|
||||
while (parent[x] != x) { parent[x] = parent[parent[x]]; x = parent[x]; }
|
||||
return x;
|
||||
};
|
||||
auto unite = [&](size_t a, size_t b) { a = find(a); b = find(b); if (a != b) parent[a] = b; };
|
||||
// Orca: Index the smaller of two consecutive layers instead of scanning every
|
||||
// pair of islands. The tree prunes distant boxes on fragmented models; exact
|
||||
// polygon intersections still decide connectivity for the remaining candidates.
|
||||
for (size_t i = 0; i + 1 < nl; ++ i) {
|
||||
m_print->throw_if_canceled();
|
||||
size_t layer_a = i, layer_b = i + 1;
|
||||
if (m_layers[layer_a]->lslices.size() < m_layers[layer_b]->lslices.size())
|
||||
std::swap(layer_a, layer_b);
|
||||
const Layer *la = m_layers[layer_a], *lb = m_layers[layer_b];
|
||||
if (lb->lslices.empty())
|
||||
continue;
|
||||
|
||||
using IslandTree = AABBTreeIndirect::Tree<2, coord_t>;
|
||||
std::vector<AABBTreeIndirect::BoundingBoxWrapper> bboxes;
|
||||
bboxes.reserve(lb->lslices.size());
|
||||
for (size_t b = 0; b < lb->lslices.size(); ++ b)
|
||||
bboxes.emplace_back(b, lb->lslices_bboxes[b]);
|
||||
IslandTree tree;
|
||||
tree.build_modify_input(bboxes);
|
||||
for (size_t a = 0; a < la->lslices.size(); ++ a) {
|
||||
const IslandTree::BoundingBox query(la->lslices_bboxes[a].min, la->lslices_bboxes[a].max);
|
||||
AABBTreeIndirect::traverse(tree,
|
||||
[&query](const IslandTree::Node &node) { return node.bbox.intersects(query); },
|
||||
[&](const IslandTree::Node &node) {
|
||||
const size_t b = node.idx;
|
||||
// Orca: Tree boxes include an epsilon, so retain the original box
|
||||
// filter. Already-connected islands cannot change the partition
|
||||
// and need no further polygon intersection.
|
||||
if (la->lslices_bboxes[a].overlap(lb->lslices_bboxes[b]) &&
|
||||
find(offset[layer_a] + a) != find(offset[layer_b] + b) &&
|
||||
! intersection_ex(la->lslices[a], lb->lslices[b]).empty())
|
||||
unite(offset[layer_a] + a, offset[layer_b] + b);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
// Orca: Full bounding box of each body, indexed by its union-find root.
|
||||
std::vector<BoundingBox> body_bbox(nreg);
|
||||
for (size_t i = 0; i < nl; ++ i)
|
||||
for (size_t a = 0; a < m_layers[i]->lslices.size(); ++ a)
|
||||
body_bbox[find(offset[i] + a)].merge(m_layers[i]->lslices_bboxes[a]);
|
||||
// Orca: Store the body bbox for every island.
|
||||
for (size_t i = 0; i < nl; ++ i) {
|
||||
Layer *layer = m_layers[i];
|
||||
layer->lslices_separated_component_bboxes.resize(layer->lslices.size());
|
||||
for (size_t a = 0; a < layer->lslices.size(); ++ a)
|
||||
layer->lslices_separated_component_bboxes[a] = body_bbox[find(offset[i] + a)];
|
||||
}
|
||||
}
|
||||
|
||||
// the following step needs to be done before combination because it may need
|
||||
// to remove only half of the combined infill
|
||||
this->bridge_over_infill();
|
||||
@@ -715,71 +809,6 @@ void PrintObject::infill()
|
||||
if (this->set_started(posInfill)) {
|
||||
m_print->set_status(35, L("Generating infill toolpath"));
|
||||
|
||||
// Orca: precompute the object's 3D connected bodies for separated infills / per-model
|
||||
// centering. Two islands belong to the same body when their slices overlap on adjacent
|
||||
// layers; islands that only overlap in top-down projection but never touch (e.g. interleaved
|
||||
// chain links) stay separate, matching "split to objects". Each layer island then records
|
||||
// the full bounding box of its body, so its infill is centered on that body as if it were
|
||||
// sliced alone. Done once here, before the parallel fill, and only when a region needs it.
|
||||
bool needs_separated_components = false;
|
||||
for (size_t i = 0; i < this->num_printing_regions(); ++ i) {
|
||||
const PrintRegionConfig &rc = this->printing_region(i).config();
|
||||
if (rc.separated_infills || rc.center_of_surface_pattern == CenterOfSurfacePattern::Each_Model) {
|
||||
needs_separated_components = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Fast path: the feature only changes anything when the object is made of more than one
|
||||
// connected body. Detect that cheaply the same way as "Split to objects" — more than one
|
||||
// model part, or a single part whose mesh is splittable (is_splittable() is cached). A single
|
||||
// body already shares the object center, i.e. the default, so skip the connectivity pass.
|
||||
if (needs_separated_components) {
|
||||
int parts = 0;
|
||||
const ModelVolume *first_part = nullptr;
|
||||
for (const ModelVolume *v : this->model_object()->volumes)
|
||||
if (v->is_model_part()) { ++ parts; first_part = v; }
|
||||
if (parts <= 1 && ! (first_part != nullptr && first_part->is_splittable()))
|
||||
needs_separated_components = false;
|
||||
}
|
||||
for (Layer *layer : m_layers)
|
||||
layer->lslices_separated_component_bboxes.clear();
|
||||
if (needs_separated_components) {
|
||||
const size_t nl = m_layers.size();
|
||||
std::vector<size_t> offset(nl + 1, 0); // flat index of the first island of each layer
|
||||
for (size_t i = 0; i < nl; ++ i)
|
||||
offset[i + 1] = offset[i] + m_layers[i]->lslices.size();
|
||||
const size_t nreg = offset[nl];
|
||||
// Union-find over every (layer, island).
|
||||
std::vector<size_t> parent(nreg);
|
||||
for (size_t i = 0; i < nreg; ++ i) parent[i] = i;
|
||||
auto find = [&parent](size_t x) {
|
||||
while (parent[x] != x) { parent[x] = parent[parent[x]]; x = parent[x]; }
|
||||
return x;
|
||||
};
|
||||
auto unite = [&](size_t a, size_t b) { a = find(a); b = find(b); if (a != b) parent[a] = b; };
|
||||
// Join islands that overlap between two consecutive layers.
|
||||
for (size_t i = 0; i + 1 < nl; ++ i) {
|
||||
const Layer *la = m_layers[i], *lb = m_layers[i + 1];
|
||||
for (size_t a = 0; a < la->lslices.size(); ++ a)
|
||||
for (size_t b = 0; b < lb->lslices.size(); ++ b)
|
||||
if (la->lslices_bboxes[a].overlap(lb->lslices_bboxes[b]) &&
|
||||
! intersection_ex(la->lslices[a], lb->lslices[b]).empty())
|
||||
unite(offset[i] + a, offset[i + 1] + b);
|
||||
}
|
||||
// Full bounding box of each body, indexed by its union-find root.
|
||||
std::vector<BoundingBox> body_bbox(nreg);
|
||||
for (size_t i = 0; i < nl; ++ i)
|
||||
for (size_t a = 0; a < m_layers[i]->lslices.size(); ++ a)
|
||||
body_bbox[find(offset[i] + a)].merge(m_layers[i]->lslices_bboxes[a]);
|
||||
// Store the body bbox for every island.
|
||||
for (size_t i = 0; i < nl; ++ i) {
|
||||
Layer *layer = m_layers[i];
|
||||
layer->lslices_separated_component_bboxes.resize(layer->lslices.size());
|
||||
for (size_t a = 0; a < layer->lslices.size(); ++ a)
|
||||
layer->lslices_separated_component_bboxes[a] = body_bbox[find(offset[i] + a)];
|
||||
}
|
||||
}
|
||||
|
||||
const auto& adaptive_fill_octree = this->m_adaptive_fill_octrees.first;
|
||||
const auto& support_fill_octree = this->m_adaptive_fill_octrees.second;
|
||||
|
||||
@@ -1516,8 +1545,6 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "infill_anchor_max"
|
||||
|| opt_key == "top_surface_line_width"
|
||||
|| opt_key == "bottom_surface_density"
|
||||
|| opt_key == "center_of_surface_pattern"
|
||||
|| opt_key == "separated_infills"
|
||||
|| opt_key == "initial_layer_line_width"
|
||||
|| opt_key == "small_area_infill_flow_compensation"
|
||||
|| opt_key == "lateral_lattice_angle_1"
|
||||
@@ -1525,6 +1552,10 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "infill_overhang_angle") {
|
||||
steps.emplace_back(posInfill);
|
||||
} else if (opt_key == "sparse_infill_pattern"
|
||||
// Orca: Body centering now also determines bridge anchors during preparation.
|
||||
// Invalidating preparation also invalidates infill, including top/bottom surfaces.
|
||||
|| opt_key == "center_of_surface_pattern"
|
||||
|| opt_key == "separated_infills"
|
||||
|| opt_key == "sparse_infill_smooth_factor"
|
||||
|| opt_key == "symmetric_infill_y_axis"
|
||||
|| opt_key == "infill_shift_step"
|
||||
@@ -1595,13 +1626,9 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
steps.emplace_back(posPerimeters);
|
||||
steps.emplace_back(posSupportMaterial);
|
||||
} else if (opt_key == "bridge_flow" || opt_key == "internal_bridge_flow") {
|
||||
if (m_config.support_top_z_distance > 0.) {
|
||||
// Only invalidate due to bridging if bridging is enabled.
|
||||
// If later "support_top_z_distance" is modified, the complete PrintObject is invalidated anyway.
|
||||
steps.emplace_back(posPerimeters);
|
||||
steps.emplace_back(posInfill);
|
||||
steps.emplace_back(posSupportMaterial);
|
||||
}
|
||||
steps.emplace_back(posPerimeters);
|
||||
steps.emplace_back(posInfill);
|
||||
steps.emplace_back(posSupportMaterial);
|
||||
} else if (
|
||||
opt_key == "wall_generator"
|
||||
|| opt_key == "wall_transition_length"
|
||||
@@ -1726,7 +1753,9 @@ bool PrintObject::invalidate_step(PrintObjectStep step)
|
||||
bool PrintObject::invalidate_all_steps()
|
||||
{
|
||||
// First call the "invalidate" functions, which may cancel background processing.
|
||||
bool result = Inherited::invalidate_all_steps() | m_print->invalidate_all_steps();
|
||||
const bool inherited_invalidated = Inherited::invalidate_all_steps();
|
||||
const bool print_invalidated = m_print->invalidate_all_steps();
|
||||
bool result = inherited_invalidated || print_invalidated;
|
||||
// Then reset some of the depending values.
|
||||
m_slicing_params.valid = false;
|
||||
m_belt_floor_z_shift_cache_valid = false;
|
||||
@@ -2789,7 +2818,7 @@ void PrintObject::bridge_over_infill()
|
||||
// SECTION to gather and filter surfaces for expanding, and then cluster them by layer
|
||||
{
|
||||
tbb::concurrent_vector<CandidateSurface> candidate_surfaces;
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, this->layers().size()), [po = static_cast<const PrintObject *>(this), &candidate_surfaces, has_lightning_infill](tbb::blocked_range<size_t> r) {
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, this->layers().size()), [po = static_cast<const PrintObject *>(this), &candidate_surfaces](tbb::blocked_range<size_t> r) {
|
||||
PRINT_OBJECT_TIME_LIMIT_MILLIS(PRINT_OBJECT_TIME_LIMIT_DEFAULT);
|
||||
for (size_t lidx = r.begin(); lidx < r.end(); lidx++) {
|
||||
const Layer *layer = po->get_layer(lidx);
|
||||
@@ -3132,21 +3161,12 @@ void PrintObject::bridge_over_infill()
|
||||
return diff(layers_sparse_infill, not_sparse_infill);
|
||||
};
|
||||
|
||||
// LAMBDA do determine optimal bridging angle
|
||||
auto determine_bridging_angle = [](const Polygons &bridged_area, const Lines &anchors, InfillPattern dominant_pattern, double infill_direction) {
|
||||
// Orca: Derive the fallback bridge direction from the supplied anchor geometry.
|
||||
// Pattern-specific angle selection belongs at the call site, where the supporting
|
||||
// layer and region are known; this helper must not override it with a base config angle.
|
||||
auto determine_bridging_angle = [](const Polygons &bridged_area, const Lines &anchors) {
|
||||
AABBTreeLines::LinesDistancer<Line> lines_tree(anchors);
|
||||
|
||||
// Orca: since 3D Honeycomb was "fixed" by forcing coordf_t layerHeight = scale_(1.0), this is no longer needed.
|
||||
// CorssHatch also does not need fixed angle.
|
||||
//
|
||||
// Check it the infill that require a fixed infill angle.
|
||||
//switch (dominant_pattern) {
|
||||
//case ip3DHoneycomb:
|
||||
//case ipCrossHatch:
|
||||
// return (infill_direction + 45.0) * 2.0 * M_PI / 360.;
|
||||
//default: break;
|
||||
//}
|
||||
|
||||
std::map<double, int> counted_directions;
|
||||
for (const Polygon &p : bridged_area) {
|
||||
double acc_distance = 0;
|
||||
@@ -3212,18 +3232,15 @@ void PrintObject::bridge_over_infill()
|
||||
if (bridging_angle == 0) {
|
||||
bridging_angle = 0.001;
|
||||
}
|
||||
switch (dominant_pattern) {
|
||||
case ipHilbertCurve: bridging_angle += 0.25 * PI; break;
|
||||
case ipOctagramSpiral: bridging_angle += (1.0 / 16.0) * PI; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
return bridging_angle;
|
||||
};
|
||||
|
||||
// LAMBDA that will fill given polygons with lines, exapand the lines to the nearest anchor, and reconstruct polygons from the newly
|
||||
// generated lines
|
||||
auto construct_anchored_polygon = [](Polygons bridged_area, Lines anchors, const Flow &bridging_flow, double bridging_angle) {
|
||||
// Orca: Extend scan sections to the nearest anchors and reconstruct the bridge area.
|
||||
// scan_spacing controls boundary sampling independently of the extrusion spacing;
|
||||
// anchoring overlap and smoothing thresholds still use the physical bridging flow.
|
||||
auto construct_anchored_polygon = [](Polygons bridged_area, Lines anchors, const Flow &bridging_flow, double bridging_angle,
|
||||
coord_t scan_spacing, bool restore_anchors = false) {
|
||||
auto lines_rotate = [](Lines &lines, double cos_angle, double sin_angle) {
|
||||
for (Line &l : lines) {
|
||||
double ax = double(l.a.x());
|
||||
@@ -3250,12 +3267,12 @@ void PrintObject::bridge_over_infill()
|
||||
BoundingBox bb_x = get_extents(bridged_area);
|
||||
BoundingBox bb_y = get_extents(anchors);
|
||||
|
||||
const size_t n_vlines = (bb_x.max.x() - bb_x.min.x() + bridging_flow.scaled_spacing() - 1) / bridging_flow.scaled_spacing();
|
||||
const size_t n_vlines = (bb_x.max.x() - bb_x.min.x() + scan_spacing - 1) / scan_spacing;
|
||||
std::vector<Line> vertical_lines(n_vlines);
|
||||
for (size_t i = 0; i < n_vlines; i++) {
|
||||
// Orca: Make sure the line is placed in the middle of the extrusion
|
||||
// coord_t x = bb_x.min.x() + i * bridging_flow.scaled_spacing();
|
||||
coord_t x = bb_x.min.x() + (i + 0.5) * bridging_flow.scaled_spacing();
|
||||
// Orca: Sample the center of each reconstructed strip. Its edges lie
|
||||
// half a scan step away, even when the sampling is finer than extrusion.
|
||||
coord_t x = bb_x.min.x() + (i + 0.5) * scan_spacing;
|
||||
coord_t y_min = bb_y.min.y() - bridging_flow.scaled_spacing();
|
||||
coord_t y_max = bb_y.max.y() + bridging_flow.scaled_spacing();
|
||||
vertical_lines[i].a = Point{x, y_min};
|
||||
@@ -3278,7 +3295,11 @@ void PrintObject::bridge_over_infill()
|
||||
auto anchors_intersections = anchors_and_walls_tree.intersections_with_line<true>(vertical_lines[i]);
|
||||
|
||||
for (Line §ion : polygon_sections[i]) {
|
||||
auto maybe_below_anchor = std::upper_bound(anchors_intersections.rbegin(), anchors_intersections.rend(), section.a,
|
||||
// Orca: A repaired boundary may already overlap its anchor by one flow width.
|
||||
// Include that overlap in the search so restoring rounded corners does not
|
||||
// extend every already anchored section into the next sparse infill cell.
|
||||
const coord_t overlap = restore_anchors ? bridging_flow.scaled_width() + SCALED_EPSILON : 0;
|
||||
auto maybe_below_anchor = std::upper_bound(anchors_intersections.rbegin(), anchors_intersections.rend(), section.a + Point{0, overlap},
|
||||
[](const Point &a, const std::pair<Point, size_t> &b) {
|
||||
return a.y() > b.first.y();
|
||||
});
|
||||
@@ -3287,7 +3308,7 @@ void PrintObject::bridge_over_infill()
|
||||
section.a.y() -= bridging_flow.scaled_width() * (0.5 + 0.5);
|
||||
}
|
||||
|
||||
auto maybe_upper_anchor = std::upper_bound(anchors_intersections.begin(), anchors_intersections.end(), section.b,
|
||||
auto maybe_upper_anchor = std::upper_bound(anchors_intersections.begin(), anchors_intersections.end(), section.b - Point{0, overlap},
|
||||
[](const Point &a, const std::pair<Point, size_t> &b) {
|
||||
return a.y() < b.first.y();
|
||||
});
|
||||
@@ -3317,7 +3338,9 @@ void PrintObject::bridge_over_infill()
|
||||
});
|
||||
}
|
||||
|
||||
// reconstruct polygon from polygon sections
|
||||
// Orca: Reconstruct the polygon from scan sections. At discontinuities and
|
||||
// strip starts/ends, use half the scan step for the X offsets; using half an
|
||||
// extrusion spacing would overlap the finer strips and distort curved anchors.
|
||||
struct TracedPoly
|
||||
{
|
||||
Points lows;
|
||||
@@ -3343,8 +3366,8 @@ void PrintObject::bridge_over_infill()
|
||||
36.0 * double(bridging_flow.scaled_spacing()) * bridging_flow.scaled_spacing()) {
|
||||
traced_poly.lows.push_back(candidate->a);
|
||||
} else {
|
||||
traced_poly.lows.push_back(traced_poly.lows.back() + Point{bridging_flow.scaled_spacing() / 2, 0});
|
||||
traced_poly.lows.push_back(candidate->a - Point{bridging_flow.scaled_spacing() / 2, 0});
|
||||
traced_poly.lows.push_back(traced_poly.lows.back() + Point{scan_spacing / 2, 0});
|
||||
traced_poly.lows.push_back(candidate->a - Point{scan_spacing / 2, 0});
|
||||
traced_poly.lows.push_back(candidate->a);
|
||||
}
|
||||
|
||||
@@ -3352,8 +3375,8 @@ void PrintObject::bridge_over_infill()
|
||||
36.0 * double(bridging_flow.scaled_spacing()) * bridging_flow.scaled_spacing()) {
|
||||
traced_poly.highs.push_back(candidate->b);
|
||||
} else {
|
||||
traced_poly.highs.push_back(traced_poly.highs.back() + Point{bridging_flow.scaled_spacing() / 2, 0});
|
||||
traced_poly.highs.push_back(candidate->b - Point{bridging_flow.scaled_spacing() / 2, 0});
|
||||
traced_poly.highs.push_back(traced_poly.highs.back() + Point{scan_spacing / 2, 0});
|
||||
traced_poly.highs.push_back(candidate->b - Point{scan_spacing / 2, 0});
|
||||
traced_poly.highs.push_back(candidate->b);
|
||||
}
|
||||
segment_added = true;
|
||||
@@ -3361,9 +3384,9 @@ void PrintObject::bridge_over_infill()
|
||||
}
|
||||
|
||||
if (!segment_added) {
|
||||
// Zero overlapping segments, we just close this polygon
|
||||
traced_poly.lows.push_back(traced_poly.lows.back() + Point{bridging_flow.scaled_spacing() / 2, 0});
|
||||
traced_poly.highs.push_back(traced_poly.highs.back() + Point{bridging_flow.scaled_spacing() / 2, 0});
|
||||
// Orca: No section continues this strip; close at its right edge.
|
||||
traced_poly.lows.push_back(traced_poly.lows.back() + Point{scan_spacing / 2, 0});
|
||||
traced_poly.highs.push_back(traced_poly.highs.back() + Point{scan_spacing / 2, 0});
|
||||
Polygon &new_poly = expanded_bridged_area.emplace_back(std::move(traced_poly.lows));
|
||||
new_poly.points.insert(new_poly.points.end(), traced_poly.highs.rbegin(), traced_poly.highs.rend());
|
||||
traced_poly.lows.clear();
|
||||
@@ -3378,9 +3401,9 @@ void PrintObject::bridge_over_infill()
|
||||
for (const auto &segment : polygon_slice) {
|
||||
if (used_segments.find(&segment) == used_segments.end()) {
|
||||
TracedPoly &new_tp = current_traced_polys.emplace_back();
|
||||
new_tp.lows.push_back(segment.a - Point{bridging_flow.scaled_spacing() / 2, 0});
|
||||
new_tp.lows.push_back(segment.a - Point{scan_spacing / 2, 0});
|
||||
new_tp.lows.push_back(segment.a);
|
||||
new_tp.highs.push_back(segment.b - Point{bridging_flow.scaled_spacing() / 2, 0});
|
||||
new_tp.highs.push_back(segment.b - Point{scan_spacing / 2, 0});
|
||||
new_tp.highs.push_back(segment.b);
|
||||
}
|
||||
}
|
||||
@@ -3487,7 +3510,10 @@ void PrintObject::bridge_over_infill()
|
||||
total_fill_area = closing(total_fill_area, float(SCALED_EPSILON));
|
||||
expansion_area = closing(expansion_area, float(SCALED_EPSILON));
|
||||
expansion_area = intersection(expansion_area, deep_infill_area);
|
||||
Polylines anchors = intersection_pl(infill_lines[lidx - 1], shrink(expansion_area, spacing));
|
||||
// Orca: Preserve the real lower-layer anchors for every candidate in this
|
||||
// layer. Replacing this shared set for one pattern also changes later regions,
|
||||
// and synthetic straight lines can claim support where no infill is printed.
|
||||
const Polylines anchors = intersection_pl(infill_lines[lidx - 1], shrink(expansion_area, spacing));
|
||||
Polygons internal_unsupported_area = shrink(deep_infill_area, spacing * 4.5);
|
||||
|
||||
#ifdef DEBUG_BRIDGE_OVER_INFILL
|
||||
@@ -3498,6 +3524,9 @@ void PrintObject::bridge_over_infill()
|
||||
std::vector<CandidateSurface> expanded_surfaces;
|
||||
expanded_surfaces.reserve(surfaces_by_layer[lidx].size());
|
||||
for (const CandidateSurface &candidate : surfaces_by_layer[lidx]) {
|
||||
const auto ®ion_config = candidate.region->region().config();
|
||||
const bool turning_pattern = region_config.sparse_infill_pattern == ipHilbertCurve ||
|
||||
region_config.sparse_infill_pattern == ipOctagramSpiral;
|
||||
const Flow &flow = candidate.region->bridging_flow(frSolidInfill, true);
|
||||
Polygons area_to_be_bridge = expand(candidate.new_polys, flow.scaled_spacing());
|
||||
area_to_be_bridge = intersection(area_to_be_bridge, deep_infill_area);
|
||||
@@ -3526,20 +3555,40 @@ void PrintObject::bridge_over_infill()
|
||||
to_lines(area_to_be_bridge), to_lines(boundary_plines), to_lines(anchors), to_lines(expansion_area));
|
||||
#endif
|
||||
|
||||
double bridging_angle = 0;
|
||||
if (!anchors.empty()) {
|
||||
bridging_angle = determine_bridging_angle(area_to_be_bridge, to_lines(anchors),
|
||||
candidate.region->region().config().sparse_infill_pattern.value,
|
||||
candidate.region->region().config().infill_direction.value);
|
||||
} else {
|
||||
// use expansion boundaries as anchors.
|
||||
// Also, use Infill pattern that is neutral for angle determination, since there are no infill lines.
|
||||
bridging_angle = determine_bridging_angle(area_to_be_bridge, to_lines(boundary_plines), InfillPattern::ipLine, 0);
|
||||
double bridging_angle = -1.;
|
||||
if (!anchors.empty() && turning_pattern) {
|
||||
// Orca: Keep adjacent bridges over Hilbert/Octagram aligned despite
|
||||
// their many local turning directions. Use the lower layer's rotation,
|
||||
// since that is the infill supporting the bridge, not the current layer's.
|
||||
for (const LayerRegion *lower_region : layer->lower_layer->regions()) {
|
||||
// Orca: Apply the configured direction only if the same region has
|
||||
// sparse infill below this bridge. A height modifier may put another
|
||||
// pattern underneath, requiring the geometry-based fallback below.
|
||||
if (&lower_region->region() != &candidate.region->region() ||
|
||||
intersection(area_to_be_bridge, to_polygons(lower_region->fill_surfaces.filter_by_type(stInternal))).empty())
|
||||
continue;
|
||||
bridging_angle = calculate_infill_rotation_angle(po, layer->lower_layer->id(), region_config.infill_direction.value,
|
||||
region_config.sparse_infill_rotate_template.value) + 0.5 * PI;
|
||||
// Orca: Apply model alignment as infill generation does, then normalize
|
||||
// the undirected bridge angle to [0, PI), including negative rotations.
|
||||
if (region_config.align_infill_direction_to_model) {
|
||||
const auto &m = po->trafo().matrix();
|
||||
bridging_angle += std::atan2(double(m(1, 0)), double(m(0, 0)));
|
||||
}
|
||||
bridging_angle = std::fmod(bridging_angle, PI);
|
||||
if (bridging_angle < 0.)
|
||||
bridging_angle += PI;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Orca: A different region below (e.g. a height modifier) needs the actual anchor
|
||||
// directions. When there are no sparse anchors, use the expansion boundaries.
|
||||
if (bridging_angle < 0.)
|
||||
bridging_angle = determine_bridging_angle(area_to_be_bridge, to_lines(anchors.empty() ? boundary_plines : anchors));
|
||||
|
||||
// ORCA: Internal bridge angle override
|
||||
// Orca: Preserve the user's absolute or relative internal bridge angle
|
||||
// override after automatic direction selection.
|
||||
if (candidate.region->region().config().internal_bridge_angle.value > 0) {
|
||||
const auto ®ion_config = candidate.region->region().config();
|
||||
const double custom_angle_rad = Geometry::deg2rad(region_config.internal_bridge_angle.value);
|
||||
if (region_config.relative_bridge_angle.value)
|
||||
bridging_angle += custom_angle_rad;
|
||||
@@ -3552,11 +3601,19 @@ void PrintObject::bridge_over_infill()
|
||||
}
|
||||
}
|
||||
|
||||
// Orca: Changing the bridge direction must not change its physical supports.
|
||||
// Extend to actual sparse infill or the existing boundary anchors, never to
|
||||
// a synthetic grid that merely has the same nominal angle and spacing.
|
||||
boundary_plines.insert(boundary_plines.end(), anchors.begin(), anchors.end());
|
||||
if (!lightning_area.empty() && !intersection(area_to_be_bridge, lightning_area).empty()) {
|
||||
boundary_plines = intersection_pl(boundary_plines, expand(area_to_be_bridge, scale_(10)));
|
||||
}
|
||||
Polygons bridging_area = construct_anchored_polygon(area_to_be_bridge, to_lines(boundary_plines), flow, bridging_angle);
|
||||
// Orca: Use four samples per extrusion spacing for Hilbert/Octagram so the
|
||||
// reconstructed boundary follows rounded anchors instead of cutting corners.
|
||||
// Keep the original step for other patterns and at least one coordinate unit
|
||||
// after integer division. This changes boundary accuracy, not infill density.
|
||||
const coord_t scan_spacing = std::max(coord_t(1), flow.scaled_spacing() / (turning_pattern ? 4 : 1));
|
||||
Polygons bridging_area = construct_anchored_polygon(area_to_be_bridge, to_lines(boundary_plines), flow, bridging_angle, scan_spacing);
|
||||
|
||||
// Check collision with other expanded surfaces
|
||||
{
|
||||
@@ -3570,7 +3627,9 @@ void PrintObject::bridge_over_infill()
|
||||
}
|
||||
}
|
||||
if (reconstruct) {
|
||||
bridging_area = construct_anchored_polygon(area_to_be_bridge, to_lines(boundary_plines), flow, bridging_angle);
|
||||
// Orca: Retain the same sampling accuracy when matching a nearby
|
||||
// bridge's direction; rebuilding must not lose the curved supports.
|
||||
bridging_area = construct_anchored_polygon(area_to_be_bridge, to_lines(boundary_plines), flow, bridging_angle, scan_spacing);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3578,6 +3637,13 @@ void PrintObject::bridge_over_infill()
|
||||
// bridging_area = opening(bridging_area, flow.scaled_spacing());
|
||||
bridging_area = opening(bridging_area, flow.scaled_spacing() * 0.75);
|
||||
bridging_area = closing(bridging_area, flow.scaled_spacing());
|
||||
// Orca: Opening/closing can pull rounded bridge ends away from their real
|
||||
// supports. Restore those contacts after smoothing, preserving the cleaned
|
||||
// area and the selected angle; do not smooth the restored contacts again.
|
||||
if (turning_pattern && !bridging_area.empty()) {
|
||||
bridging_area = union_(bridging_area, construct_anchored_polygon(bridging_area, to_lines(boundary_plines), flow,
|
||||
bridging_angle, scan_spacing, true));
|
||||
}
|
||||
bridging_area = intersection(bridging_area, limiting_area);
|
||||
bridging_area = intersection(bridging_area, total_fill_area);
|
||||
bridging_area = diff(bridging_area, total_top_area);
|
||||
|
||||
Reference in New Issue
Block a user