mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-15 23:12:08 +00:00
Merge branch 'main' into pr/Noisyfox/13712
This commit is contained in:
@@ -232,7 +232,7 @@ void PrintObject::_transform_hole_to_polyholes()
|
||||
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);
|
||||
std::tuple<Point, float, int, coord_t, bool>{center, diameter_max, layer->m_regions[region_idx]->region().config().outer_wall_filament_id.value, max_variation, twist}, & hole);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -316,18 +316,29 @@ std::vector<std::set<int>> PrintObject::detect_extruder_geometric_unprintables()
|
||||
continue;
|
||||
for (auto layerm : layer->regions()) {
|
||||
auto region = layerm->region();
|
||||
int wall_filament = region.config().wall_filament;
|
||||
int solid_infill_filament = region.config().solid_infill_filament;
|
||||
int sparse_infill_filament = region.config().sparse_infill_filament;
|
||||
int outer_wall_filament_id = region.config().outer_wall_filament_id;
|
||||
int inner_wall_filament_id = region.config().inner_wall_filament_id;
|
||||
int internal_solid_filament_id = region.config().internal_solid_filament_id;
|
||||
int top_surface_filament_id = region.config().top_surface_filament_id;
|
||||
int bottom_surface_filament_id = region.config().bottom_surface_filament_id;
|
||||
int sparse_infill_filament_id = region.config().sparse_infill_filament_id;
|
||||
|
||||
if (!layerm->fills.entities.empty()) {
|
||||
if (solid_infill_filament > 0)
|
||||
geometric_unprintables[extruder_id].insert(solid_infill_filament - 1);
|
||||
if (sparse_infill_filament > 0)
|
||||
geometric_unprintables[extruder_id].insert(sparse_infill_filament - 1);
|
||||
if (internal_solid_filament_id > 0)
|
||||
geometric_unprintables[extruder_id].insert(internal_solid_filament_id - 1);
|
||||
if (top_surface_filament_id > 0)
|
||||
geometric_unprintables[extruder_id].insert(top_surface_filament_id - 1);
|
||||
if (bottom_surface_filament_id > 0)
|
||||
geometric_unprintables[extruder_id].insert(bottom_surface_filament_id - 1);
|
||||
if (sparse_infill_filament_id > 0)
|
||||
geometric_unprintables[extruder_id].insert(sparse_infill_filament_id - 1);
|
||||
}
|
||||
if (!layerm->perimeters.entities.empty()) {
|
||||
if (outer_wall_filament_id > 0)
|
||||
geometric_unprintables[extruder_id].insert(outer_wall_filament_id - 1);
|
||||
if (inner_wall_filament_id > 0)
|
||||
geometric_unprintables[extruder_id].insert(inner_wall_filament_id - 1);
|
||||
}
|
||||
if (!layerm->perimeters.entities.empty() && wall_filament > 0)
|
||||
geometric_unprintables[extruder_id].insert(wall_filament - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -352,21 +363,28 @@ std::vector<std::set<int>> PrintObject::detect_extruder_geometric_unprintables()
|
||||
auto layer = m_layers[j];
|
||||
for (auto layerm : layer->regions()) {
|
||||
const auto& region = layerm->region();
|
||||
int wall_filament = region.config().wall_filament;
|
||||
int solid_infill_filament = region.config().solid_infill_filament;
|
||||
int sparse_infill_filament = region.config().sparse_infill_filament;
|
||||
int outer_wall_filament_id = region.config().outer_wall_filament_id;
|
||||
int inner_wall_filament_id = region.config().inner_wall_filament_id;
|
||||
int internal_solid_filament_id = region.config().internal_solid_filament_id;
|
||||
int top_surface_filament_id = region.config().top_surface_filament_id;
|
||||
int bottom_surface_filament_id = region.config().bottom_surface_filament_id;
|
||||
int sparse_infill_filament_id = region.config().sparse_infill_filament_id;
|
||||
std::optional<ExPolygons> fill_expolys;
|
||||
BoundingBox fill_bbox;
|
||||
std::optional<ExPolygons> wall_expolys;
|
||||
BoundingBox wall_bbox;
|
||||
|
||||
for (size_t idx = 0; idx < unprintable_area_in_obj_coord.size(); ++idx) {
|
||||
bool do_infill_filament_detect = (solid_infill_filament > 0 && tbb_geometric_unprintables[idx].count(solid_infill_filament - 1) == 0) ||
|
||||
(sparse_infill_filament > 0 && tbb_geometric_unprintables[idx].count(sparse_infill_filament-1) == 0);
|
||||
bool do_infill_filament_detect = (internal_solid_filament_id > 0 && tbb_geometric_unprintables[idx].count(internal_solid_filament_id - 1) == 0) ||
|
||||
(top_surface_filament_id > 0 && tbb_geometric_unprintables[idx].count(top_surface_filament_id - 1) == 0) ||
|
||||
(bottom_surface_filament_id > 0 && tbb_geometric_unprintables[idx].count(bottom_surface_filament_id - 1) == 0) ||
|
||||
(sparse_infill_filament_id > 0 && tbb_geometric_unprintables[idx].count(sparse_infill_filament_id-1) == 0);
|
||||
|
||||
bool infill_unprintable = !layerm->fills.entities.empty() &&
|
||||
((solid_infill_filament > 0 && tbb_geometric_unprintables[idx].count(solid_infill_filament - 1) > 0) ||
|
||||
(sparse_infill_filament > 0 && tbb_geometric_unprintables[idx].count(sparse_infill_filament - 1) > 0));
|
||||
((internal_solid_filament_id > 0 && tbb_geometric_unprintables[idx].count(internal_solid_filament_id - 1) > 0) ||
|
||||
(top_surface_filament_id > 0 && tbb_geometric_unprintables[idx].count(top_surface_filament_id - 1) > 0) ||
|
||||
(bottom_surface_filament_id > 0 && tbb_geometric_unprintables[idx].count(bottom_surface_filament_id - 1) > 0) ||
|
||||
(sparse_infill_filament_id > 0 && tbb_geometric_unprintables[idx].count(sparse_infill_filament_id - 1) > 0));
|
||||
|
||||
if (!layerm->fills.entities.empty() && do_infill_filament_detect) {
|
||||
if (!fill_expolys) {
|
||||
@@ -375,19 +393,27 @@ std::vector<std::set<int>> PrintObject::detect_extruder_geometric_unprintables()
|
||||
}
|
||||
if (fill_bbox.overlap(unprintable_area_bbox[idx]) &&
|
||||
!intersection(*fill_expolys, unprintable_area_in_obj_coord[idx]).empty()) {
|
||||
if (solid_infill_filament > 0)
|
||||
tbb_geometric_unprintables[idx].insert(solid_infill_filament - 1);
|
||||
if (sparse_infill_filament > 0)
|
||||
tbb_geometric_unprintables[idx].insert(sparse_infill_filament - 1);
|
||||
if (internal_solid_filament_id > 0)
|
||||
tbb_geometric_unprintables[idx].insert(internal_solid_filament_id - 1);
|
||||
if (top_surface_filament_id > 0)
|
||||
tbb_geometric_unprintables[idx].insert(top_surface_filament_id - 1);
|
||||
if (bottom_surface_filament_id > 0)
|
||||
tbb_geometric_unprintables[idx].insert(bottom_surface_filament_id - 1);
|
||||
if (sparse_infill_filament_id > 0)
|
||||
tbb_geometric_unprintables[idx].insert(sparse_infill_filament_id - 1);
|
||||
infill_unprintable = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool do_wall_filament_detect = wall_filament > 0 && tbb_geometric_unprintables[idx].count(wall_filament - 1) == 0;
|
||||
if (!layerm->perimeters.entities.empty() && do_wall_filament_detect) {
|
||||
bool do_outer_wall_filament_detect = outer_wall_filament_id > 0 && tbb_geometric_unprintables[idx].count(outer_wall_filament_id - 1) == 0;
|
||||
bool do_inner_wall_filament_detect = inner_wall_filament_id > 0 && tbb_geometric_unprintables[idx].count(inner_wall_filament_id - 1) == 0;
|
||||
if (!layerm->perimeters.entities.empty() && (do_outer_wall_filament_detect || do_inner_wall_filament_detect)) {
|
||||
// if infill is unprintable, no need to check wall since wall contour surrounds infill contour
|
||||
if (infill_unprintable) {
|
||||
tbb_geometric_unprintables[idx].insert(wall_filament - 1);
|
||||
if (outer_wall_filament_id > 0)
|
||||
tbb_geometric_unprintables[idx].insert(outer_wall_filament_id - 1);
|
||||
if (inner_wall_filament_id > 0)
|
||||
tbb_geometric_unprintables[idx].insert(inner_wall_filament_id - 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -402,7 +428,10 @@ std::vector<std::set<int>> PrintObject::detect_extruder_geometric_unprintables()
|
||||
|
||||
if (wall_bbox.overlap(unprintable_area_bbox[idx]) &&
|
||||
!intersection(*wall_expolys, unprintable_area_in_obj_coord[idx]).empty()) {
|
||||
tbb_geometric_unprintables[idx].insert(wall_filament - 1);
|
||||
if (outer_wall_filament_id > 0)
|
||||
tbb_geometric_unprintables[idx].insert(outer_wall_filament_id - 1);
|
||||
if (inner_wall_filament_id > 0)
|
||||
tbb_geometric_unprintables[idx].insert(inner_wall_filament_id - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1265,8 +1294,10 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "bottom_shell_thickness"
|
||||
|| opt_key == "top_shell_thickness"
|
||||
|| opt_key == "minimum_sparse_infill_area"
|
||||
|| opt_key == "sparse_infill_filament"
|
||||
|| opt_key == "solid_infill_filament"
|
||||
|| opt_key == "sparse_infill_filament_id"
|
||||
|| opt_key == "internal_solid_filament_id"
|
||||
|| opt_key == "top_surface_filament_id"
|
||||
|| opt_key == "bottom_surface_filament_id"
|
||||
|| opt_key == "sparse_infill_line_width"
|
||||
|| opt_key == "skin_infill_line_width"
|
||||
|| opt_key == "skeleton_infill_line_width"
|
||||
@@ -1277,7 +1308,9 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "ensure_vertical_shell_thickness"
|
||||
|| opt_key == "bridge_angle"
|
||||
|| opt_key == "internal_bridge_angle" // ORCA: Internal bridge angle override
|
||||
|| opt_key == "relative_bridge_angle" // ORCA: Relative bridge angle
|
||||
//BBS
|
||||
|| opt_key == "bridge_line_width"
|
||||
|| opt_key == "bridge_density"
|
||||
|| opt_key == "internal_bridge_density") {
|
||||
steps.emplace_back(posPrepareInfill);
|
||||
@@ -1302,6 +1335,9 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "infill_shift_step"
|
||||
|| opt_key == "sparse_infill_rotate_template"
|
||||
|| opt_key == "solid_infill_rotate_template"
|
||||
|| opt_key == "lightning_overhang_angle"
|
||||
|| opt_key == "lightning_prune_angle"
|
||||
|| opt_key == "lightning_straightening_angle"
|
||||
|| opt_key == "skeleton_infill_density"
|
||||
|| opt_key == "skin_infill_density"
|
||||
|| opt_key == "infill_lock_depth"
|
||||
@@ -1324,7 +1360,8 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
steps.emplace_back(posPrepareInfill);
|
||||
} else if (
|
||||
opt_key == "outer_wall_line_width"
|
||||
|| opt_key == "wall_filament"
|
||||
|| opt_key == "outer_wall_filament_id"
|
||||
|| opt_key == "inner_wall_filament_id"
|
||||
|| opt_key == "fuzzy_skin"
|
||||
|| opt_key == "fuzzy_skin_thickness"
|
||||
|| opt_key == "fuzzy_skin_point_distance"
|
||||
@@ -1705,30 +1742,46 @@ void PrintObject::detect_surfaces_type()
|
||||
// Only iterate to the second-to-last layer, since we look at layer i+1.
|
||||
if( (this->config().enable_extra_bridge_layer.value == eblApplyToAll) || (this->config().enable_extra_bridge_layer.value == eblExternalBridgeOnly)){
|
||||
const size_t last = (m_layers.empty() ? 0 : m_layers.size() - 1);
|
||||
tbb::parallel_for( tbb::blocked_range<size_t>(0, last), [this, region_id](const tbb::blocked_range<size_t> &range) {
|
||||
|
||||
// ORCA: Two-phase split (collect-then-apply) to eliminate a data race in the
|
||||
// original single-phase parallel_for, where iteration `i` rewrote
|
||||
// m_layers[i+1]->slices.surfaces via std::move while iteration `i+1` (running
|
||||
// on an adjacent TBB block on another worker thread) was iterating that same
|
||||
// Surfaces vector as its bot_surfs. Splitting into a read-only collect pass
|
||||
// followed by a write-only apply pass removes the cross-iteration aliasing.
|
||||
//
|
||||
// Phase 1: read-only pass — collect each layer's stBottomBridge polygons into a
|
||||
// per-layer cache. No surfaces are mutated, so concurrent reads are safe.
|
||||
std::vector<Polygons> bridge_polys_per_layer(last);
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, last), [this, region_id, &bridge_polys_per_layer](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t i = range.begin(); i < range.end(); ++i) {
|
||||
m_print->throw_if_canceled();
|
||||
|
||||
// Step 1: Find bridge polygons
|
||||
// Current layer (i): Search for stBottomBridge polygons.
|
||||
const Surfaces &bot_surfs = m_layers[i]->m_regions[region_id]->slices.surfaces;
|
||||
// Next layer (i+1): The layer where stInternal polygons may be re-classified.
|
||||
Surfaces &top_surfs = m_layers[i + 1]->m_regions[region_id]->slices.surfaces;
|
||||
|
||||
// Step 2: Collect the bridge polygons in the current layer region
|
||||
Polygons polygons_bridge;
|
||||
for (const Surface &sbot : bot_surfs) {
|
||||
if (sbot.surface_type == stBottomBridge) {
|
||||
polygons_append(polygons_bridge, to_polygons(sbot));
|
||||
polygons_append(bridge_polys_per_layer[i], to_polygons(sbot));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
// Phase 2: write pass — each iteration mutates only m_layers[i+1]->slices.surfaces
|
||||
// and reads its bridge polygons from the precomputed cache. Different iterations
|
||||
// never share a write target, so there is no aliasing between worker threads.
|
||||
tbb::parallel_for( tbb::blocked_range<size_t>(0, last), [this, region_id, &bridge_polys_per_layer](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t i = range.begin(); i < range.end(); ++i) {
|
||||
m_print->throw_if_canceled();
|
||||
|
||||
// Step 1 + 2: pull the precomputed bridge polygons for the current source layer.
|
||||
const Polygons &polygons_bridge = bridge_polys_per_layer[i];
|
||||
|
||||
// Step 3: Early termination of loop if no meaningfull bridge found
|
||||
// No bridge polygons found, continue to the next layer
|
||||
if (polygons_bridge.empty())
|
||||
continue;
|
||||
|
||||
// Step 4: Bottom bridge polygons found - scan and create layer+1 bridge polygon
|
||||
Surfaces &top_surfs = m_layers[i + 1]->m_regions[region_id]->slices.surfaces;
|
||||
Surfaces new_surfaces;
|
||||
new_surfaces.reserve(top_surfs.size());
|
||||
|
||||
@@ -1742,7 +1795,50 @@ void PrintObject::detect_surfaces_type()
|
||||
// This would also skip generation of very short dual bridge layers (that are shorter than N perimeters), but these are unecessary as the bridge distance is
|
||||
// We could reduce this slightly to account for innacurcies in the clipping operation.
|
||||
// TODO: Monitor GitHub issues to check whether second bridge layers are ommited where they should be generated. If yes, reduce the filtering distance
|
||||
|
||||
|
||||
// ORCA: Same-layer-top guard.
|
||||
//
|
||||
// Collect every stTop polygon present at layer i+1 (this region) and
|
||||
// expand it by the same `offset_distance` used by the bridge filter
|
||||
// above. Note that `offset_distance` here is the full wall-band
|
||||
// distance for the region (external wall width + all configured
|
||||
// internal wall widths, i.e. external + (wall_loops - 1) × internal),
|
||||
// not a single perimeter width. Any candidate second-bridge area that
|
||||
// falls under this expanded mask will be subtracted out below.
|
||||
//
|
||||
// Why this exists: detect_surfaces_type() classifies a layer's "top"
|
||||
// surfaces as the geometry that is not covered by the layer above. Those
|
||||
// stTop regions often have small stInternal islands embedded in them.
|
||||
// The pre-existing wall-band filter (shrink_ex/offset_ex by
|
||||
// offset_distance) is supposed to throw those tiny islands away, but
|
||||
// its result is sensitive to Clipper's floating-point order of
|
||||
// operations: on macOS ARM the filter eats them, on Windows/Intel it
|
||||
// doesn't. Visible bridges then show up scattered across the printed
|
||||
// top surface.
|
||||
//
|
||||
// Expanding stTop by offset_distance and subtracting it from the
|
||||
// overlap makes the decision platform-independent: an island fully
|
||||
// surrounded by stTop disappears regardless of which Clipper happens
|
||||
// to be doing the math, while large stInternal regions away from the
|
||||
// top survive intact (the expansion only nibbles the wall-band depth
|
||||
// inward).
|
||||
//
|
||||
// Keep ExPolygons throughout so that any holes inside an stTop surface
|
||||
// are offset with the correct sign (positive offset shrinks holes /
|
||||
// grows the solid region). Using Polygons + expand() would treat the
|
||||
// contour and each hole as independent polygons and could distort the
|
||||
// mask.
|
||||
ExPolygons same_layer_top_expanded;
|
||||
{
|
||||
ExPolygons same_layer_top;
|
||||
for (const Surface &s : top_surfs) {
|
||||
if (s.surface_type == stTop)
|
||||
same_layer_top.push_back(s.expolygon);
|
||||
}
|
||||
if (! same_layer_top.empty())
|
||||
same_layer_top_expanded = offset_ex(same_layer_top, offset_distance);
|
||||
}
|
||||
|
||||
// For each surface in the layer above
|
||||
for (Surface &s_up : top_surfs) {
|
||||
// Only reclassify stInternal polygons (i.e. what will become later solid and sparse infill)
|
||||
@@ -1757,7 +1853,13 @@ void PrintObject::detect_surfaces_type()
|
||||
// Filter out the resulting candidate bridges based on size. First perform a shrink operation...
|
||||
// ...followed by an expand operation to bring them back to the original size (positive offset)
|
||||
overlap = offset_ex(shrink_ex(overlap, offset_distance), offset_distance);
|
||||
|
||||
|
||||
// ORCA: subtract the expanded same-layer stTop mask (see comment above
|
||||
// the mask construction). Drops stInternal islands fully surrounded by
|
||||
// stTop at i+1 without affecting bridges that lie away from the top.
|
||||
if (! same_layer_top_expanded.empty() && ! overlap.empty())
|
||||
overlap = diff_ex(overlap, same_layer_top_expanded, ApplySafetyOffset::Yes);
|
||||
|
||||
// Now subtract the filtered new bridge layer from the remaining internal surfaces to create the new internal surface
|
||||
ExPolygons remainder = diff_ex(p_up, overlap, ApplySafetyOffset::Yes);
|
||||
|
||||
@@ -2502,29 +2604,76 @@ void PrintObject::bridge_over_infill()
|
||||
backup_surfaces[lidx] = {};
|
||||
}
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, this->layers().size()), [po = this, &backup_surfaces,
|
||||
&surfaces_by_layer](tbb::blocked_range<size_t> r) {
|
||||
// ORCA: Two-phase split (collect-then-apply) to eliminate a data race in
|
||||
// the original single-phase parallel_for, where iteration `lidx` read
|
||||
// m_layers[lidx-1]->regions()->fill_surfaces (its lower_layer) to compute
|
||||
// `lightning_fill`, while iteration `lidx-1`, on an adjacent TBB block,
|
||||
// was concurrently std::move-ing / emplace_back-ing into that same
|
||||
// SurfaceCollection.
|
||||
//
|
||||
// Semantic choice — read ORIGINAL surfaces in Phase 1:
|
||||
// The lower_layer that iteration `lidx` looks at is the *current* layer
|
||||
// for iteration `lidx-1`, which Phase 2 will modify. We therefore have to
|
||||
// pick whether Phase 1 sees that layer's pre-modification or
|
||||
// post-modification state. We deliberately use the original (pre-modification)
|
||||
// state, for two reasons:
|
||||
// 1. The gate is asking "does the layer below use lightning sparse
|
||||
// infill?" — that's a property of the layer's configuration plus its
|
||||
// original sparse-infill classification. Phase 2's edits only carve
|
||||
// a small overhang-aligned slice of sparse into solid; they do not
|
||||
// change whether the layer is using lightning. The realistic gate
|
||||
// answer is the same either way.
|
||||
// 2. Each layer's solid expansion is meant to give its OWN lower_layer
|
||||
// something to anchor lightning lines onto. Cascading the gate
|
||||
// across layers ("skip mine because the layer below already did
|
||||
// some") would invert that intent and force a serial Phase 2.
|
||||
// The original racy code didn't actually implement either choice
|
||||
// consistently — it returned whichever bytes happened to be in the
|
||||
// vector when the thread arrived. This split makes the behaviour
|
||||
// defined, deterministic across runs and platforms, and equivalent to
|
||||
// a clean sequential implementation that gathered all gates first and
|
||||
// then applied modifications.
|
||||
//
|
||||
// Phase 1: read-only — for each layer, determine whether its lower_layer
|
||||
// has any stInternal area inside a lightning-infill region. That's the
|
||||
// sole purpose of `lightning_fill` in the original code: a gate. Capture
|
||||
// it once into a per-layer bool, derived from the original (unmodified)
|
||||
// surfaces, so the gate is platform-independent and order-independent.
|
||||
std::vector<char> needs_lightning_expansion(this->layers().size(), 0);
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, this->layers().size()), [po = this, &surfaces_by_layer,
|
||||
&needs_lightning_expansion](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++) {
|
||||
if (surfaces_by_layer.find(lidx) == surfaces_by_layer.end())
|
||||
continue;
|
||||
|
||||
Layer *layer = po->get_layer(lidx);
|
||||
const Layer *layer = po->get_layer(lidx);
|
||||
const Layer *lower_layer = layer->lower_layer;
|
||||
if (lower_layer == nullptr)
|
||||
continue;
|
||||
|
||||
Polygons lightning_fill;
|
||||
for (const LayerRegion *region : lower_layer->regions()) {
|
||||
if (region->region().config().sparse_infill_pattern == ipLightning) {
|
||||
Polygons lf = to_polygons(region->fill_surfaces.filter_by_type(stInternal));
|
||||
lightning_fill.insert(lightning_fill.end(), lf.begin(), lf.end());
|
||||
if (region->region().config().sparse_infill_pattern == ipLightning
|
||||
&& ! region->fill_surfaces.filter_by_type(stInternal).empty()) {
|
||||
needs_lightning_expansion[lidx] = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (lightning_fill.empty())
|
||||
// Phase 2: write-only — each iteration mutates only m_layers[lidx]'s
|
||||
// fill_surfaces and never reads any other layer's surfaces. Different
|
||||
// iterations write to disjoint LayerRegion::fill_surfaces vectors, so
|
||||
// there is no aliasing between worker threads.
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, this->layers().size()), [po = this, &backup_surfaces,
|
||||
&surfaces_by_layer,
|
||||
&needs_lightning_expansion](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++) {
|
||||
if (! needs_lightning_expansion[lidx])
|
||||
continue;
|
||||
|
||||
Layer *layer = po->get_layer(lidx);
|
||||
|
||||
for (LayerRegion *region : layer->regions()) {
|
||||
backup_surfaces[lidx][region] = std::move(
|
||||
region->fill_surfaces); // Make backup copy by move!! so that pointers in candidate surfaces stay valid
|
||||
@@ -3104,8 +3253,19 @@ void PrintObject::bridge_over_infill()
|
||||
}
|
||||
|
||||
// ORCA: Internal bridge angle override
|
||||
if (candidate.region->region().config().internal_bridge_angle > 0)
|
||||
bridging_angle = candidate.region->region().config().internal_bridge_angle.value * PI / 180.0; // Convert degrees to radians
|
||||
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;
|
||||
else {
|
||||
bridging_angle = custom_angle_rad;
|
||||
if (region_config.align_infill_direction_to_model) {
|
||||
auto m = po->trafo().matrix();
|
||||
bridging_angle += std::atan2((double)m(1, 0), (double)m(0, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boundary_plines.insert(boundary_plines.end(), anchors.begin(), anchors.end());
|
||||
if (!lightning_area.empty() && !intersection(area_to_be_bridge, lightning_area).empty()) {
|
||||
@@ -3234,53 +3394,75 @@ void PrintObject::bridge_over_infill()
|
||||
// === ORCA: Create a second internal bridge layer above the first bridge layer. ========================================================
|
||||
// ======================================================================================================================================
|
||||
if ( this->m_config.enable_extra_bridge_layer == eblApplyToAll || this->m_config.enable_extra_bridge_layer == eblInternalBridgeOnly) {
|
||||
// Process layers in parallel up to second-to-last
|
||||
tbb::parallel_for( tbb::blocked_range<size_t>(0, this->layers().size() - 1), [this](const tbb::blocked_range<size_t>& r) {
|
||||
for (size_t lidx = r.begin(); lidx < r.end(); ++lidx)
|
||||
{
|
||||
// ORCA: Two-phase to eliminate the same data race as the external-bridge
|
||||
// pass in detect_surfaces_type().
|
||||
//
|
||||
// Phase 1: read-only — for each layer, collect its stInternalBridge polygons and
|
||||
// the matching bridge angle into a per-layer cache.
|
||||
struct LayerBridgeCache {
|
||||
ExPolygons polys;
|
||||
double angle = 0.0;
|
||||
float offset_distance = 0.0f;
|
||||
bool has_bridge = false;
|
||||
};
|
||||
// Guard against size_t underflow when the object has 0 or 1 layers — there is
|
||||
// no "layer above" to receive an extra bridge, so the whole pass is a no-op.
|
||||
const size_t last = (this->layers().size() < 2) ? 0 : this->layers().size() - 1;
|
||||
std::vector<LayerBridgeCache> caches(this->layers().size());
|
||||
|
||||
tbb::parallel_for( tbb::blocked_range<size_t>(0, last), [this, &caches](const tbb::blocked_range<size_t>& r) {
|
||||
for (size_t lidx = r.begin(); lidx < r.end(); ++lidx) {
|
||||
Layer* layer = this->get_layer(lidx);
|
||||
|
||||
// (A) Gather internal bridging surfaces in the current layer
|
||||
ExPolygons bridging_current_layer;
|
||||
double bridging_angle_current = 0.0;
|
||||
|
||||
bool found_any_bridge = false;
|
||||
float offset_distance = 0.0f;
|
||||
|
||||
// Pick a region from which to retrieve the flow width
|
||||
LayerBridgeCache &cache = caches[lidx];
|
||||
|
||||
if (!layer->regions().empty())
|
||||
offset_distance = layer->regions().front()->flow(frSolidInfill).scaled_width();
|
||||
|
||||
cache.offset_distance = layer->regions().front()->flow(frSolidInfill).scaled_width();
|
||||
for (LayerRegion *region : layer->regions()) {
|
||||
for (const Surface &surf : region->fill_surfaces.surfaces) {
|
||||
if (surf.surface_type == stInternalBridge) {
|
||||
bridging_current_layer.push_back(surf.expolygon);
|
||||
bridging_angle_current = surf.bridge_angle; // Store the last bridging angle of the current print object
|
||||
found_any_bridge = true;
|
||||
cache.polys.push_back(surf.expolygon);
|
||||
cache.angle = surf.bridge_angle; // last bridge angle on this layer wins, matching prior behaviour
|
||||
cache.has_bridge = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no bridging in this layer, continue with the next
|
||||
if (!found_any_bridge || bridging_current_layer.empty())
|
||||
|
||||
if (!cache.has_bridge || cache.polys.empty()) {
|
||||
cache.has_bridge = false;
|
||||
continue;
|
||||
|
||||
// (B) Shrink-expand to remove trivial bridging areas
|
||||
bridging_current_layer = offset_ex( shrink_ex(bridging_current_layer, offset_distance), offset_distance );
|
||||
|
||||
if (bridging_current_layer.empty())
|
||||
continue; // all bridging was trivial, continue with the next layer
|
||||
|
||||
}
|
||||
|
||||
// Shrink-expand to remove trivial bridging areas
|
||||
cache.polys = offset_ex(shrink_ex(cache.polys, cache.offset_distance), cache.offset_distance);
|
||||
if (cache.polys.empty())
|
||||
cache.has_bridge = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Phase 2: write — each iteration mutates only m_layers[lidx+1]->fill_surfaces and
|
||||
// pulls its bridge polygons from the precomputed cache. Different iterations never
|
||||
// touch the same fill_surfaces vector, so there is no aliasing between workers.
|
||||
tbb::parallel_for( tbb::blocked_range<size_t>(0, last), [this, &caches](const tbb::blocked_range<size_t>& r) {
|
||||
for (size_t lidx = r.begin(); lidx < r.end(); ++lidx)
|
||||
{
|
||||
const LayerBridgeCache &cache = caches[lidx];
|
||||
|
||||
// If no bridging in this layer, continue with the next
|
||||
if (!cache.has_bridge || cache.polys.empty())
|
||||
continue;
|
||||
|
||||
// (C) If there is a next layer, identify overlapping stInternal & stInternalSolid areas and convert the overlap to stSecondInternalBridge
|
||||
if (lidx + 1 < this->layers().size()) {
|
||||
Layer* next_layer = this->get_layer(lidx + 1);
|
||||
|
||||
// second bridging angle is 90 degrees offset
|
||||
double bridging_angle_second = bridging_angle_current + M_PI / 2.0;
|
||||
|
||||
double bridging_angle_second = cache.angle + M_PI / 2.0;
|
||||
// Union the bridging polygons
|
||||
ExPolygons bridging_union = union_safety_offset_ex(bridging_current_layer);
|
||||
|
||||
ExPolygons bridging_union = union_safety_offset_ex(cache.polys);
|
||||
const float offset_distance = cache.offset_distance;
|
||||
|
||||
for (LayerRegion *next_region : next_layer->regions()) {
|
||||
Surfaces next_new_surfaces;
|
||||
Surfaces keep_surfaces;
|
||||
@@ -3379,6 +3561,12 @@ static void clamp_exturder_to_default(ConfigOptionInt &opt, size_t num_extruders
|
||||
opt.value = 1;
|
||||
}
|
||||
|
||||
static void clamp_feature_filament_to_valid(ConfigOptionInt &opt, size_t num_extruders)
|
||||
{
|
||||
if (opt.value <= 0 || opt.value > (int)num_extruders)
|
||||
opt.value = 1;
|
||||
}
|
||||
|
||||
PrintObjectConfig PrintObject::object_config_from_model_object(const PrintObjectConfig &default_object_config, const ModelObject &object, size_t num_extruders)
|
||||
{
|
||||
PrintObjectConfig config = default_object_config;
|
||||
@@ -3394,63 +3582,124 @@ PrintObjectConfig PrintObject::object_config_from_model_object(const PrintObject
|
||||
}
|
||||
|
||||
const std::string key_extruder { "extruder" };
|
||||
static constexpr const std::initializer_list<const std::string_view> keys_extruders { "sparse_infill_filament"sv, "solid_infill_filament"sv, "wall_filament"sv };
|
||||
static constexpr const std::initializer_list<const std::string_view> keys_extruders {
|
||||
"sparse_infill_filament_id"sv,
|
||||
"internal_solid_filament_id"sv,
|
||||
"top_surface_filament_id"sv,
|
||||
"bottom_surface_filament_id"sv,
|
||||
"outer_wall_filament_id"sv,
|
||||
"inner_wall_filament_id"sv
|
||||
};
|
||||
|
||||
static void apply_to_print_region_config(PrintRegionConfig &out, const DynamicPrintConfig &in)
|
||||
struct FeatureFilamentOverrideMask
|
||||
{
|
||||
// 1) Map legacy "extruder" to feature filament keys as a fallback only.
|
||||
// If any feature-specific filament is explicitly set, keep those values.
|
||||
bool sparse_infill_filament_id = false;
|
||||
bool internal_solid_filament_id = false;
|
||||
bool top_surface_filament_id = false;
|
||||
bool bottom_surface_filament_id = false;
|
||||
bool outer_wall_filament_id = false;
|
||||
bool inner_wall_filament_id = false;
|
||||
};
|
||||
|
||||
static void apply_to_print_region_config(PrintRegionConfig &out, const DynamicPrintConfig &in, FeatureFilamentOverrideMask &feature_overrides)
|
||||
{
|
||||
// 1) Explicit feature filament values take precedence over base extruder fallback.
|
||||
auto *opt_extruder = in.opt<ConfigOptionInt>(key_extruder);
|
||||
auto *opt_sparse_infill_filament = in.opt<ConfigOptionInt>("sparse_infill_filament");
|
||||
auto *opt_solid_infill_filament = in.opt<ConfigOptionInt>("solid_infill_filament");
|
||||
auto *opt_wall_filament = in.opt<ConfigOptionInt>("wall_filament");
|
||||
const bool has_feature_filament_override =
|
||||
(opt_sparse_infill_filament != nullptr && opt_sparse_infill_filament->value > 0) ||
|
||||
(opt_solid_infill_filament != nullptr && opt_solid_infill_filament->value > 0) ||
|
||||
(opt_wall_filament != nullptr && opt_wall_filament->value > 0);
|
||||
if (opt_extruder)
|
||||
if (int extruder = opt_extruder->value; extruder > 1 && ! has_feature_filament_override) {
|
||||
// Not a default extruder.
|
||||
out.sparse_infill_filament.value = extruder;
|
||||
out.solid_infill_filament.value = extruder;
|
||||
out.wall_filament.value = extruder;
|
||||
}
|
||||
int base_extruder = (opt_extruder != nullptr) ? opt_extruder->value : 0;
|
||||
|
||||
// 2) Copy the rest of the values.
|
||||
for (auto it = in.cbegin(); it != in.cend(); ++ it)
|
||||
if (it->first != key_extruder)
|
||||
if (ConfigOption* my_opt = out.option(it->first, false); my_opt != nullptr) {
|
||||
if (one_of(it->first, keys_extruders)) {
|
||||
// Ignore "default" extruders.
|
||||
// "Default" (0) clears explicit override for this scope and lets fallback apply.
|
||||
int extruder = static_cast<const ConfigOptionInt*>(it->second.get())->value;
|
||||
if (extruder > 0)
|
||||
if (extruder > 0) {
|
||||
my_opt->setInt(extruder);
|
||||
if (it->first == "sparse_infill_filament_id")
|
||||
feature_overrides.sparse_infill_filament_id = true;
|
||||
else if (it->first == "internal_solid_filament_id")
|
||||
feature_overrides.internal_solid_filament_id = true;
|
||||
else if (it->first == "top_surface_filament_id")
|
||||
feature_overrides.top_surface_filament_id = true;
|
||||
else if (it->first == "bottom_surface_filament_id")
|
||||
feature_overrides.bottom_surface_filament_id = true;
|
||||
else if (it->first == "outer_wall_filament_id")
|
||||
feature_overrides.outer_wall_filament_id = true;
|
||||
else if (it->first == "inner_wall_filament_id")
|
||||
feature_overrides.inner_wall_filament_id = true;
|
||||
} else {
|
||||
if (it->first == "sparse_infill_filament_id")
|
||||
feature_overrides.sparse_infill_filament_id = false;
|
||||
else if (it->first == "internal_solid_filament_id")
|
||||
feature_overrides.internal_solid_filament_id = false;
|
||||
else if (it->first == "top_surface_filament_id")
|
||||
feature_overrides.top_surface_filament_id = false;
|
||||
else if (it->first == "bottom_surface_filament_id")
|
||||
feature_overrides.bottom_surface_filament_id = false;
|
||||
else if (it->first == "outer_wall_filament_id")
|
||||
feature_overrides.outer_wall_filament_id = false;
|
||||
else if (it->first == "inner_wall_filament_id")
|
||||
feature_overrides.inner_wall_filament_id = false;
|
||||
}
|
||||
} else
|
||||
my_opt->set(it->second.get());
|
||||
}
|
||||
|
||||
// 3) Apply base extruder only to features that were not explicitly overridden.
|
||||
if (base_extruder > 0) {
|
||||
if (!feature_overrides.sparse_infill_filament_id)
|
||||
out.sparse_infill_filament_id.value = base_extruder;
|
||||
if (!feature_overrides.internal_solid_filament_id)
|
||||
out.internal_solid_filament_id.value = base_extruder;
|
||||
if (!feature_overrides.top_surface_filament_id)
|
||||
out.top_surface_filament_id.value = base_extruder;
|
||||
if (!feature_overrides.bottom_surface_filament_id)
|
||||
out.bottom_surface_filament_id.value = base_extruder;
|
||||
if (!feature_overrides.outer_wall_filament_id)
|
||||
out.outer_wall_filament_id.value = base_extruder;
|
||||
if (!feature_overrides.inner_wall_filament_id)
|
||||
out.inner_wall_filament_id.value = base_extruder;
|
||||
}
|
||||
}
|
||||
|
||||
PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &default_or_parent_region_config, const DynamicPrintConfig *layer_range_config, const ModelVolume &volume, size_t num_extruders)
|
||||
{
|
||||
PrintRegionConfig config = default_or_parent_region_config;
|
||||
FeatureFilamentOverrideMask feature_overrides;
|
||||
|
||||
// For model parts, non-zero values coming from the print defaults should stay explicit.
|
||||
if (volume.is_model_part()) {
|
||||
feature_overrides.sparse_infill_filament_id = (config.sparse_infill_filament_id.value > 0);
|
||||
feature_overrides.internal_solid_filament_id = (config.internal_solid_filament_id.value > 0);
|
||||
feature_overrides.top_surface_filament_id = (config.top_surface_filament_id.value > 0);
|
||||
feature_overrides.bottom_surface_filament_id = (config.bottom_surface_filament_id.value > 0);
|
||||
feature_overrides.outer_wall_filament_id = (config.outer_wall_filament_id.value > 0);
|
||||
feature_overrides.inner_wall_filament_id = (config.inner_wall_filament_id.value > 0);
|
||||
}
|
||||
|
||||
if (volume.is_model_part()) {
|
||||
// default_or_parent_region_config contains the Print's PrintRegionConfig.
|
||||
// Override with ModelObject's PrintRegionConfig values.
|
||||
apply_to_print_region_config(config, volume.get_object()->config.get());
|
||||
apply_to_print_region_config(config, volume.get_object()->config.get(), feature_overrides);
|
||||
} else {
|
||||
// default_or_parent_region_config contains parent PrintRegion config, which already contains ModelVolume's config.
|
||||
}
|
||||
apply_to_print_region_config(config, volume.config.get());
|
||||
apply_to_print_region_config(config, volume.config.get(), feature_overrides);
|
||||
if (! volume.material_id().empty())
|
||||
apply_to_print_region_config(config, volume.material()->config.get());
|
||||
apply_to_print_region_config(config, volume.material()->config.get(), feature_overrides);
|
||||
if (layer_range_config != nullptr) {
|
||||
// Not applicable to modifiers.
|
||||
assert(volume.is_model_part());
|
||||
apply_to_print_region_config(config, *layer_range_config);
|
||||
apply_to_print_region_config(config, *layer_range_config, feature_overrides);
|
||||
}
|
||||
// Clamp invalid extruders to the default extruder (with index 1).
|
||||
clamp_exturder_to_default(config.sparse_infill_filament, num_extruders);
|
||||
clamp_exturder_to_default(config.wall_filament, num_extruders);
|
||||
clamp_exturder_to_default(config.solid_infill_filament, num_extruders);
|
||||
// Resolve feature defaults and clamp invalid extruders to index 1.
|
||||
clamp_feature_filament_to_valid(config.sparse_infill_filament_id, num_extruders);
|
||||
clamp_feature_filament_to_valid(config.outer_wall_filament_id, num_extruders);
|
||||
clamp_feature_filament_to_valid(config.inner_wall_filament_id, num_extruders);
|
||||
clamp_feature_filament_to_valid(config.internal_solid_filament_id, num_extruders);
|
||||
clamp_feature_filament_to_valid(config.top_surface_filament_id, num_extruders);
|
||||
clamp_feature_filament_to_valid(config.bottom_surface_filament_id, num_extruders);
|
||||
if (config.sparse_infill_density.value < 0.00011f)
|
||||
// Switch of infill for very low infill rates, also avoid division by zero in infill generator for these very low rates.
|
||||
// See GH issue #5910.
|
||||
@@ -3513,9 +3762,12 @@ SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full
|
||||
object_config.brim_type != btNoBrim && object_config.brim_width > 0.,
|
||||
object_extruders);
|
||||
for (const std::pair<const t_layer_height_range, ModelConfig> &range_and_config : model_object.layer_config_ranges)
|
||||
if (range_and_config.second.has("wall_filament") ||
|
||||
range_and_config.second.has("sparse_infill_filament") ||
|
||||
range_and_config.second.has("solid_infill_filament"))
|
||||
if (range_and_config.second.has("outer_wall_filament_id") ||
|
||||
range_and_config.second.has("inner_wall_filament_id") ||
|
||||
range_and_config.second.has("sparse_infill_filament_id") ||
|
||||
range_and_config.second.has("internal_solid_filament_id") ||
|
||||
range_and_config.second.has("top_surface_filament_id") ||
|
||||
range_and_config.second.has("bottom_surface_filament_id"))
|
||||
PrintRegion::collect_object_printing_extruders(
|
||||
print_config,
|
||||
region_config_from_model_volume(default_region_config, &range_and_config.second.get(), *model_volume, filament_extruders),
|
||||
@@ -3932,8 +4184,8 @@ void PrintObject::combine_infill()
|
||||
// Limit the number of combined layers to the maximum height allowed by this regions' nozzle.
|
||||
//FIXME limit the layer height to max_layer_height
|
||||
double nozzle_diameter = std::min(
|
||||
this->print()->config().nozzle_diameter.get_at(region.config().sparse_infill_filament.value - 1),
|
||||
this->print()->config().nozzle_diameter.get_at(region.config().solid_infill_filament.value - 1));
|
||||
this->print()->config().nozzle_diameter.get_at(region.config().sparse_infill_filament_id.value - 1),
|
||||
this->print()->config().nozzle_diameter.get_at(region.config().internal_solid_filament_id.value - 1));
|
||||
|
||||
//Orca: Limit combination of infill to up to infill_combination_max_layer_height
|
||||
const double infill_combination_max_layer_height = region.config().infill_combination_max_layer_height.get_abs_value(nozzle_diameter);
|
||||
|
||||
Reference in New Issue
Block a user