mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-14 19:31:14 +00:00
Precise Seam: new full-coverage policy for Seam Blocked (#16170)
* Precise Seam: new full-coverage policy for Seam Blocked A Seam Blocked modifier that covers a whole perimeter now blocks it, like a perimeter painted red all round, instead of being skipped. It overrides painting and lower-priority modifiers, like Enforced and Neutral. A new warning names the modifier: "Seam Blocked modifier "<name>" of "<object>" covers an entire perimeter". It is shown only when a perimeter stays entirely Blocked after all weak modifiers. The full-containment warning now covers only Center, Left and Right. * Precise Seam: fix seams on perimeters blocked all round - SeamComparator::is_first_not_much_worse() compares the types first, so two Blocked candidates fall through like Neutral ones. On a perimeter blocked all round, by a whole Seam Blocked modifier or by red painting, Random put the seam on the closing vertex of every layer and Aligned stopped aligning. - The whole-perimeter Blocked warning flags only the highest whole zone, the one in effect. A lower whole zone it overwrites is no longer reported, and the candidate scan runs only when that zone is Blocked. - The warning msgids take the type name from the menu's "Seam Blocked" msgid. - PreciseSeamWarnings::set() shares the load-before-store idiom, and every weak segment records its modifier. - Tests: a whole Blocked perimeter gets the seams of an unmarked one under every seam position; overwritten whole Blocked zones are not reported. --------- Co-authored-by: Ian Bassi <ian.bassi@outlook.com>
This commit is contained in:
co-authored by
Ian Bassi
parent
008ee792f6
commit
5a427f2eb6
@@ -673,7 +673,7 @@ static void refine_at_vertex(
|
||||
}
|
||||
}
|
||||
|
||||
// Records one extraction for the warnings: the type of a modifier with a discarded fragment, and the
|
||||
// Records one extraction for the warnings: the type of a modifier with a discarded fragment, the
|
||||
// evaluated/reached flags for "had no effect" (a discarded fragment counts as reached, a contact not).
|
||||
static void record_extraction(PreciseSeamWarnings *warnings, const ModelVolume *modifier,
|
||||
const SegmentExtraction &extracted)
|
||||
@@ -685,13 +685,10 @@ static void record_extraction(PreciseSeamWarnings *warnings, const ModelVolume *
|
||||
const auto it = warnings->modifier_usage.find(modifier);
|
||||
if (it == warnings->modifier_usage.end())
|
||||
return;
|
||||
// Load before store: most calls find the flag already set, so shared cache lines stay clean.
|
||||
PreciseSeamWarnings::ModifierUsage &usage = it->second;
|
||||
if (!usage.checked.load(std::memory_order_relaxed))
|
||||
usage.checked.store(true, std::memory_order_relaxed);
|
||||
const bool reached = !extracted.segments.empty() || extracted.full_containment || extracted.discarded_fragments > 0;
|
||||
if (reached && !usage.reached.load(std::memory_order_relaxed))
|
||||
usage.reached.store(true, std::memory_order_relaxed);
|
||||
PreciseSeamWarnings::set(usage.checked);
|
||||
if (!extracted.segments.empty() || extracted.full_containment || extracted.discarded_fragments > 0)
|
||||
PreciseSeamWarnings::set(usage.reached);
|
||||
}
|
||||
|
||||
// Strong priority is per modifier, never a global maximum across different modifiers.
|
||||
@@ -953,22 +950,15 @@ std::vector<WeakModifierSegment> collect_weak_modifier_segments(
|
||||
const SegmentExtraction extracted = extract_perimeter_segments(prepared, it->second[layer_id], modifier_volume->type(), context);
|
||||
record_extraction(warnings, modifier_volume, extracted);
|
||||
const auto type = convert_weak_modifier_type(modifier_volume->type());
|
||||
// Full containment works like seam painting: Enforced and Neutral type the whole perimeter;
|
||||
// Blocked is skipped with a warning, since the seam cannot avoid the whole perimeter.
|
||||
// Full containment works like seam painting all round: the whole perimeter takes the type.
|
||||
// A perimeter left whole Blocked also gets a warning (see apply_weak_modifiers_to_perimeter()).
|
||||
if (extracted.full_containment) {
|
||||
if (type == EnforcedBlockedSeamPoint::Blocked) {
|
||||
if (warnings)
|
||||
PreciseSeamWarnings::mark(warnings->full_containment, modifier_volume->type());
|
||||
continue;
|
||||
}
|
||||
WeakModifierSegment whole{type, Point(), PerimeterPosition{0, 0.}, Point(), PerimeterPosition{0, 0.}};
|
||||
whole.whole_perimeter = true;
|
||||
result.push_back(whole);
|
||||
result.push_back({type, Point(), PerimeterPosition{0, 0.}, Point(), PerimeterPosition{0, 0.}, true, modifier_volume});
|
||||
continue;
|
||||
}
|
||||
for (const PerimeterSegment &segment : extracted.segments) {
|
||||
result.push_back({type, segment.polyline.points.front(), segment.begin,
|
||||
segment.polyline.points.back(), segment.end});
|
||||
segment.polyline.points.back(), segment.end, false, modifier_volume});
|
||||
}
|
||||
}
|
||||
// Boundary insertion, refinement and last-write-wins priority use the established path.
|
||||
@@ -979,7 +969,8 @@ void apply_weak_modifiers_to_perimeter(
|
||||
const std::vector<WeakModifierSegment> &weak_segments,
|
||||
PrintObjectSeamData::LayerSeams &result,
|
||||
const SeamPlacerImpl::Perimeter &perimeter,
|
||||
bool &some_point_enforced)
|
||||
bool &some_point_enforced,
|
||||
PreciseSeamWarnings *warnings)
|
||||
{
|
||||
// Get z-coordinate for unscaling boundary points
|
||||
const float z_coord = result.points[perimeter.start_index].position.z();
|
||||
@@ -1029,6 +1020,19 @@ void apply_weak_modifiers_to_perimeter(
|
||||
some_point_enforced = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Only the highest whole zone is in effect, and a higher band may still unblock part of it.
|
||||
if (warnings == nullptr)
|
||||
return;
|
||||
const auto last_whole = std::find_if(weak_segments.rbegin(), weak_segments.rend(),
|
||||
[](const WeakModifierSegment &segment) { return segment.whole_perimeter; });
|
||||
if (last_whole == weak_segments.rend() || last_whole->type != EnforcedBlockedSeamPoint::Blocked)
|
||||
return;
|
||||
for (size_t i = perimeter.start_index; i < perimeter.end_index; ++i)
|
||||
if (result.points[i].type != EnforcedBlockedSeamPoint::Blocked)
|
||||
return;
|
||||
if (const auto usage = warnings->modifier_usage.find(last_whole->modifier); usage != warnings->modifier_usage.end())
|
||||
PreciseSeamWarnings::set(usage->second.fully_blocked);
|
||||
}
|
||||
|
||||
// Restore precise seam positions that may have been modified
|
||||
|
||||
@@ -49,18 +49,20 @@ struct PreciseSeamWarnings {
|
||||
// Masks of the Precise Seam types that caused each warning reason, one bit per type (type_bit()).
|
||||
// The user warning lists the types instead of naming modifiers.
|
||||
std::atomic<unsigned> multiple_intersections{0}; // Center/Left/Right with several segments on a perimeter.
|
||||
std::atomic<unsigned> full_containment{0}; // Skipped for a perimeter fully inside: Center/Left/Right, Blocked.
|
||||
std::atomic<unsigned> full_containment{0}; // Skipped for a perimeter fully inside: Center/Left/Right.
|
||||
std::atomic<unsigned> failed_types{0}; // Types with at least one discarded fragment.
|
||||
std::atomic<size_t> failed_fragments{0}; // Total discarded fragments, for the log summary.
|
||||
// Fragments saved by the rare-case fallback or accepted as contacts; log only, no user warning.
|
||||
// Clipper is deterministic, so a prismatic model can repeat the same case on every layer.
|
||||
std::atomic<size_t> recovered_fragments{0};
|
||||
|
||||
// Per-modifier flags for the "had no effect" warning. Modifiers are registered before the parallel
|
||||
// phase, so workers only set flags; unregistered ones (e.g. in tests) are not tracked.
|
||||
// Per-modifier flags for the "had no effect" and "covers an entire perimeter" warnings. Modifiers are
|
||||
// registered before the parallel phase, so workers only set flags; unregistered ones (e.g. in tests)
|
||||
// are not tracked.
|
||||
struct ModifierUsage {
|
||||
std::atomic<bool> checked{false}; // Extracted on at least one perimeter.
|
||||
std::atomic<bool> reached{false}; // Gave a segment, full containment or a discarded fragment.
|
||||
std::atomic<bool> checked{false}; // Extracted on at least one perimeter.
|
||||
std::atomic<bool> reached{false}; // Gave a segment, full containment or a discarded fragment.
|
||||
std::atomic<bool> fully_blocked{false}; // Seam Blocked whole zone in effect on a perimeter left Blocked.
|
||||
};
|
||||
std::unordered_map<const ModelVolume*, ModifierUsage> modifier_usage;
|
||||
|
||||
@@ -70,13 +72,18 @@ struct PreciseSeamWarnings {
|
||||
assert(is_precise_seam(type));
|
||||
return 1u << (int(type) - int(ModelVolumeType::PRECISE_SEAM_CENTER));
|
||||
}
|
||||
// Load before fetch_or: most calls find the bit already set, so shared cache lines stay clean.
|
||||
// Load before writing: most calls find the bit or flag already set, so shared cache lines stay clean.
|
||||
static void mark(std::atomic<unsigned> &mask, ModelVolumeType type)
|
||||
{
|
||||
const unsigned bit = type_bit(type);
|
||||
if ((mask.load(std::memory_order_relaxed) & bit) == 0)
|
||||
mask.fetch_or(bit, std::memory_order_relaxed);
|
||||
}
|
||||
static void set(std::atomic<bool> &flag)
|
||||
{
|
||||
if (!flag.load(std::memory_order_relaxed))
|
||||
flag.store(true, std::memory_order_relaxed);
|
||||
}
|
||||
};
|
||||
|
||||
// Optional caller identity for concise diagnostics when an intersection is discarded.
|
||||
@@ -139,9 +146,10 @@ struct WeakModifierSegment {
|
||||
PerimeterPosition left_position; // Position on the source perimeter before insertion/refinement.
|
||||
Point right_point; // Coordinates of right (last) point of segment
|
||||
PerimeterPosition right_position; // Retained provenance, not an index into the modified polygon.
|
||||
// Full containment of an Enforced or Neutral modifier: the zone is the whole perimeter, without
|
||||
// boundaries (the points and positions above are unused and nothing is inserted for it).
|
||||
// Full containment of a weak modifier: the zone is the whole perimeter, without boundaries
|
||||
// (the points and positions above are unused and nothing is inserted for it).
|
||||
bool whole_perimeter = false;
|
||||
const ModelVolume *modifier = nullptr; // Source modifier, for the warnings.
|
||||
};
|
||||
|
||||
// Collects the object's Precise Seam volumes: strong ones in priority order, weak ones in application
|
||||
@@ -173,12 +181,14 @@ std::vector<WeakModifierSegment> collect_weak_modifier_segments(
|
||||
PreciseSeamWarnings* warnings = nullptr);
|
||||
|
||||
// Retypes the candidates inside each zone in the given order (pass zones lowest priority first);
|
||||
// sets some_point_enforced when an Enforced zone applies.
|
||||
// sets some_point_enforced when an Enforced zone applies. When the whole perimeter ends up Blocked and
|
||||
// the highest whole-perimeter zone is Blocked, flags that zone's modifier for the warning.
|
||||
void apply_weak_modifiers_to_perimeter(
|
||||
const std::vector<WeakModifierSegment> &weak_segments,
|
||||
PrintObjectSeamData::LayerSeams &result,
|
||||
const SeamPlacerImpl::Perimeter &perimeter,
|
||||
bool &some_point_enforced);
|
||||
bool &some_point_enforced,
|
||||
PreciseSeamWarnings *warnings = nullptr);
|
||||
|
||||
// Restore precise seam positions that may have been modified by alignment
|
||||
// Iterates through all perimeters and restores precise_seam_point positions
|
||||
|
||||
@@ -608,7 +608,7 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, const
|
||||
// Apply weak modifiers if no strong modifier was inserted
|
||||
if (!inserted_seam_position.has_value() && !weak_segments.empty()) {
|
||||
PreciseSeam::apply_weak_modifiers_to_perimeter(
|
||||
weak_segments, result, perimeter, some_point_enforced);
|
||||
weak_segments, result, perimeter, some_point_enforced, warnings);
|
||||
}
|
||||
|
||||
if (some_point_enforced) {
|
||||
@@ -896,18 +896,15 @@ struct SeamComparator {
|
||||
return a.central_enforcer;
|
||||
}
|
||||
|
||||
if (a.type == EnforcedBlockedSeamPoint::Enforced) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (a.type == EnforcedBlockedSeamPoint::Blocked) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.type != b.type) {
|
||||
return a.type > b.type;
|
||||
}
|
||||
|
||||
// Two Blocked points fall through like Neutral ones, so a perimeter blocked all round still gets a proper seam.
|
||||
if (a.type == EnforcedBlockedSeamPoint::Enforced) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//avoid overhangs
|
||||
if ((a.overhang > 0.0f || b.overhang > 0.0f)
|
||||
&& abs(a.overhang - b.overhang) > (0.1f * a.perimeter.flow_width)) {
|
||||
@@ -1660,18 +1657,42 @@ void SeamPlacer::init(const Print &print, std::function<void(void)> throw_if_can
|
||||
parts.push_back((boost::format(_u8L("multiple intersections with a perimeter, only one was used (%1%)")) % type_list(mi)).str());
|
||||
if (fc != 0)
|
||||
parts.push_back((boost::format(_u8L("a perimeter is fully inside a modifier, the modifier was not applied to it (%1%)")) % type_list(fc)).str());
|
||||
// Modifiers evaluated somewhere that never reached a perimeter; never-evaluated ones are not reported.
|
||||
// Print and volume order make the named one deterministic; the log lists them all.
|
||||
std::vector<const ModelVolume*> no_effect;
|
||||
for (const PrintObject *po : print.objects())
|
||||
for (const ModelVolume *volume : po->model_object()->volumes) {
|
||||
const auto it = precise_seam_warnings.modifier_usage.find(volume);
|
||||
if (it != precise_seam_warnings.modifier_usage.end() &&
|
||||
it->second.checked.load(std::memory_order_relaxed) &&
|
||||
!it->second.reached.load(std::memory_order_relaxed) &&
|
||||
std::find(no_effect.begin(), no_effect.end(), volume) == no_effect.end())
|
||||
no_effect.push_back(volume);
|
||||
// Modifiers whose usage flags match, in print and volume order, so the named one is deterministic.
|
||||
const auto collect_modifiers = [&print, &precise_seam_warnings](const auto &matches) {
|
||||
std::vector<const ModelVolume*> found;
|
||||
for (const PrintObject *po : print.objects())
|
||||
for (const ModelVolume *volume : po->model_object()->volumes) {
|
||||
const auto it = precise_seam_warnings.modifier_usage.find(volume);
|
||||
if (it != precise_seam_warnings.modifier_usage.end() && matches(it->second) &&
|
||||
std::find(found.begin(), found.end(), volume) == found.end())
|
||||
found.push_back(volume);
|
||||
}
|
||||
return found;
|
||||
};
|
||||
// Seam Blocked modifiers whose whole perimeter stayed Blocked; the user warning names only the
|
||||
// first one, the log lists them all.
|
||||
const std::vector<const ModelVolume*> fully_blocked = collect_modifiers(
|
||||
[](const PreciseSeam::PreciseSeamWarnings::ModifierUsage &usage) { return usage.fully_blocked.load(std::memory_order_relaxed); });
|
||||
for (const ModelVolume *volume : fully_blocked)
|
||||
BOOST_LOG_TRIVIAL(warning) << "[PreciseSeamFullyBlocked] object=\"" << volume->get_object()->name
|
||||
<< "\" modifier=\"" << volume->name << "\"";
|
||||
if (!fully_blocked.empty()) {
|
||||
const ModelVolume *first = fully_blocked.front();
|
||||
if (fully_blocked.size() == 1) {
|
||||
// TRN: %1% is the modifier type "Seam Blocked" as in the menu, %2% the modifier name, %3% the object name.
|
||||
parts.push_back((boost::format(_u8L("%1% modifier \"%2%\" of \"%3%\" covers an entire perimeter"))
|
||||
% _u8L("Seam Blocked") % first->name % first->get_object()->name).str());
|
||||
} else {
|
||||
// TRN: %1% is the modifier type "Seam Blocked" as in the menu, %2% the modifier name, %3% the object name, %4% the number of modifiers.
|
||||
parts.push_back((boost::format(_u8L("%1% modifier \"%2%\" of \"%3%\" (%4% in total) covers an entire perimeter"))
|
||||
% _u8L("Seam Blocked") % first->name % first->get_object()->name % fully_blocked.size()).str());
|
||||
}
|
||||
}
|
||||
// Modifiers evaluated somewhere that never reached a perimeter; never-evaluated ones are not reported.
|
||||
const std::vector<const ModelVolume*> no_effect = collect_modifiers(
|
||||
[](const PreciseSeam::PreciseSeamWarnings::ModifierUsage &usage) {
|
||||
return usage.checked.load(std::memory_order_relaxed) && !usage.reached.load(std::memory_order_relaxed);
|
||||
});
|
||||
// The user warning names only the first one; the log lists them all.
|
||||
for (const ModelVolume *volume : no_effect)
|
||||
BOOST_LOG_TRIVIAL(warning) << "[PreciseSeamNoEffect] object=\"" << volume->get_object()->name
|
||||
|
||||
Reference in New Issue
Block a user