#ifndef slic3r_PreciseSeam_hpp_ #define slic3r_PreciseSeam_hpp_ #include #include #include #include #include #include #include #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; using ModifierSlices = std::vector; // Per-volume slices with cached bounds, shared read-only by both modifier kinds. using ModifierRegionsCache = std::unordered_map; // 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 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 multiple_intersections{0}; // Center/Left/Right with several segments on a perimeter. std::atomic full_containment{0}; // Skipped for a perimeter fully inside: Center/Left/Right, Blocked. std::atomic failed_types{0}; // Types with at least one discarded fragment. std::atomic 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 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. struct ModifierUsage { std::atomic checked{false}; // Extracted on at least one perimeter. std::atomic reached{false}; // Gave a segment, full containment or a discarded fragment. }; std::unordered_map 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 fetch_or: most calls find the bit already set, so shared cache lines stay clean. static void mark(std::atomic &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); } }; // 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 edge_indices; PerimeterPosition begin; PerimeterPosition end; std::optional 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 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 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). bool whole_perimeter = false; }; // 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& strong_volumes_out, std::vector& 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 insert_strong_seam_point( const std::vector &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 collect_weak_modifier_segments( const std::vector &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. void apply_weak_modifiers_to_perimeter( const std::vector &weak_segments, PrintObjectSeamData::LayerSeams &result, const SeamPlacerImpl::Perimeter &perimeter, bool &some_point_enforced); // 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 &layers); } // namespace PreciseSeam } // namespace Slic3r #endif // slic3r_PreciseSeam_hpp_