mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-14 11:21:18 +00:00
* 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>
201 lines
9.0 KiB
C++
201 lines
9.0 KiB
C++
#ifndef slic3r_PreciseSeam_hpp_
|
|
#define slic3r_PreciseSeam_hpp_
|
|
|
|
#include <atomic>
|
|
#include <cassert>
|
|
#include <cstddef>
|
|
#include <optional>
|
|
#include <vector>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
#include "libslic3r/BoundingBox.hpp"
|
|
#include "libslic3r/ExPolygon.hpp"
|
|
#include "libslic3r/Point.hpp"
|
|
#include "libslic3r/Polygon.hpp"
|
|
#include "libslic3r/Polyline.hpp"
|
|
#include "libslic3r/Model.hpp"
|
|
#include "SeamPlacer.hpp"
|
|
|
|
// Precise Seam: helper volumes that decide where the seam goes on external perimeters.
|
|
// Design: docs/HLSD/precise-seam.md
|
|
|
|
namespace Slic3r {
|
|
namespace PreciseSeam {
|
|
|
|
// Geometry and its exterior bounds are prepared together, then treated as read-only.
|
|
struct ModifierRegion {
|
|
ExPolygon polygon;
|
|
BoundingBox bounds;
|
|
|
|
explicit ModifierRegion(ExPolygon region)
|
|
: polygon(std::move(region)), bounds(polygon.contour.points) {}
|
|
};
|
|
|
|
using ModifierRegions = std::vector<ModifierRegion>;
|
|
using ModifierSlices = std::vector<ModifierRegions>;
|
|
// Per-volume slices with cached bounds, shared read-only by both modifier kinds.
|
|
using ModifierRegionsCache = std::unordered_map<const ModelVolume*, ModifierSlices>;
|
|
|
|
// Move sliced geometry into the cache without detaching holes or changing layer indices.
|
|
ModifierRegions prepare_modifier_regions(ExPolygons regions);
|
|
ModifierSlices prepare_modifier_slices(std::vector<ExPolygons> slices);
|
|
|
|
// Bound diagnostic volume only; every failed or recovered fragment is still counted and handled.
|
|
// The same limit applies separately to failure and recovery markers.
|
|
inline constexpr size_t failed_fragment_log_limit = 10;
|
|
|
|
// Shared by all layers and objects in one SeamPlacer::init(); a new pass starts fresh.
|
|
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.
|
|
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" 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> fully_blocked{false}; // Seam Blocked whole zone in effect on a perimeter left Blocked.
|
|
};
|
|
std::unordered_map<const ModelVolume*, ModifierUsage> modifier_usage;
|
|
|
|
// Bit of a Precise Seam type in the masks above, in menu order (Center is bit 0).
|
|
static unsigned type_bit(ModelVolumeType type)
|
|
{
|
|
assert(is_precise_seam(type));
|
|
return 1u << (int(type) - int(ModelVolumeType::PRECISE_SEAM_CENTER));
|
|
}
|
|
// 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.
|
|
struct ExtractionContext {
|
|
const Layer *layer = nullptr;
|
|
const ModelVolume *modifier = nullptr;
|
|
PreciseSeamWarnings *warnings = nullptr;
|
|
};
|
|
|
|
// Borrows the source polygon; use only until insertion/refinement changes that polygon.
|
|
struct PreparedPerimeter {
|
|
const Polygon &polygon;
|
|
BoundingBox bounds;
|
|
Polyline line;
|
|
bool valid = false;
|
|
|
|
explicit PreparedPerimeter(const Polygon &perimeter);
|
|
};
|
|
|
|
// A vertex is represented by its outgoing edge and parameter zero, including vertex 0.
|
|
struct PerimeterPosition {
|
|
size_t edge_index;
|
|
double parameter;
|
|
};
|
|
|
|
// Prepared against the immutable perimeter, before insertion shifts its edge indices.
|
|
struct StrongSeamTarget {
|
|
Point point;
|
|
size_t edge_index;
|
|
};
|
|
|
|
struct PerimeterSegment {
|
|
Polyline polyline;
|
|
// One bound source edge per polyline interval.
|
|
std::vector<size_t> edge_indices;
|
|
PerimeterPosition begin;
|
|
PerimeterPosition end;
|
|
std::optional<StrongSeamTarget> strong_target; // Absent for weak modifiers and full containment.
|
|
// Scaled arc length, calculated only for Center or comparison of multiple strong segments.
|
|
double length = 0.; // Zero means unmeasured for weak, full containment, and a single Left/Right segment.
|
|
};
|
|
|
|
struct SegmentExtraction {
|
|
std::vector<PerimeterSegment> segments;
|
|
bool full_containment = false;
|
|
bool valid = true; // Invalid perimeter input; discarded fragments do not invalidate other segments.
|
|
// Clipped fragments whose binding failed; one segment may consist of several fragments.
|
|
size_t discarded_fragments = 0; // Failed bindings are ignored, with a warning and diagnostic marker.
|
|
};
|
|
|
|
// Clips a prepared, unchanged perimeter (>= 3 vertices, no consecutive duplicates, either direction)
|
|
// against a modifier's regions and returns its segments; strong targets are prepared only where needed.
|
|
SegmentExtraction extract_perimeter_segments(const PreparedPerimeter &prepared, const ModifierRegions &modifier,
|
|
ModelVolumeType mode, const ExtractionContext &context = {});
|
|
|
|
// Result of weak modifier segment processing
|
|
struct WeakModifierSegment {
|
|
SeamPlacerImpl::EnforcedBlockedSeamPoint type; // Enforced/Blocked/Neutral
|
|
Point left_point; // Coordinates of left (first) point of segment
|
|
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 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
|
|
// order. Call once per object in SeamPlacer::init() before gathering candidates.
|
|
void init_precise_seam_data(
|
|
std::vector<const ModelVolume*>& strong_volumes_out,
|
|
std::vector<const ModelVolume*>& weak_volumes_out,
|
|
bool& has_strong_out,
|
|
const ModelObject* model_object);
|
|
|
|
// Inserts the seam point of the first strong modifier with a usable segment on this perimeter and returns
|
|
// it, or nullopt. `prepared` must describe `polygon` before any change.
|
|
std::optional<Point> insert_strong_seam_point(
|
|
const std::vector<const ModelVolume*> &strong_volumes,
|
|
Polygon &polygon,
|
|
const PreparedPerimeter &prepared,
|
|
const Layer *layer,
|
|
const ModifierRegionsCache &slices_cache,
|
|
PreciseSeamWarnings* warnings = nullptr);
|
|
|
|
// Collects weak zones, inserts their boundaries into `polygon` and subdivides enforced edges. Pass
|
|
// modifiers lowest priority first; `prepared` must describe the unchanged `polygon`.
|
|
std::vector<WeakModifierSegment> collect_weak_modifier_segments(
|
|
const std::vector<const ModelVolume*> &weak_volumes,
|
|
Polygon &polygon,
|
|
const PreparedPerimeter &prepared,
|
|
const Layer *layer,
|
|
const ModifierRegionsCache &slices_cache,
|
|
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. 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,
|
|
PreciseSeamWarnings *warnings = nullptr);
|
|
|
|
// Restore precise seam positions that may have been modified by alignment
|
|
// Iterates through all perimeters and restores precise_seam_point positions
|
|
void restore_precise_seam_positions(std::vector<PrintObjectSeamData::LayerSeams> &layers);
|
|
|
|
} // namespace PreciseSeam
|
|
} // namespace Slic3r
|
|
|
|
#endif // slic3r_PreciseSeam_hpp_
|