mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-15 20:01:31 +00:00
Merge upstream/main into belt-printer
Brings belt-printer up to main4b4a261787. Resolutions: - G-code header (#15897, #15915): main moved the header, config and thumbnail block later in _do_export; write_belt_header() moves with it, still after the thumbnails and outside the BTT_TFT gate. - _extrude: first-layer acceleration keeps the per-path first-layer plane test with main's cached nozzle index (#16028); main's set_speed out-param form (#16108) everywhere else. - GCodeWriter (#16108): the arc-to-polyline fallback for machine mappings that cannot express G2/G3 now runs in the out-param extrude_arc_to_xy, which is the overload GCode calls, and appends to the caller's string. - GCodeProcessorResult: the belt fields join main's forwarding assign. - Clipper2 (#15969): belt arrange helpers take Slic3r::Point; the tree support join types lose their ClipperLib qualifier. - CLI arrange (#15837): belt printers still reserve no wipe tower. - Wipe tower options (#15841): the two new sparse-layer toggles are hidden for belt printers like the rest of the tower options. - Keyboard shortcuts (#15706): main's registry replaces the old key switch; the belt view toggle is re-registered in the next commit. - Print::process: the belt purge-plan undo runs before main's SliceStarted event. - scripts/filament_id_snapshot.json: deleted on main (a77209af8f). - Includes and appended tests: union of both sides.
This commit is contained in:
@@ -1,14 +1,39 @@
|
||||
#include "SeamPlacer.hpp"
|
||||
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/AABBTreeIndirect.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/Flow.hpp"
|
||||
#include "libslic3r/Line.hpp"
|
||||
#include "libslic3r/ExPolygon.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include "libslic3r/PrintBase.hpp"
|
||||
#include "PrintConfig.hpp"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include "libslic3r/TriangleSelector.hpp"
|
||||
#include "tbb/parallel_for.h"
|
||||
#include "tbb/blocked_range.h"
|
||||
#include "tbb/parallel_reduce.h"
|
||||
#include <atomic>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/format.hpp>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <cstddef>
|
||||
#include <igl/Hit.h>
|
||||
#include <optional>
|
||||
#include <cassert>
|
||||
#include <functional>
|
||||
#include <numeric>
|
||||
#include <memory>
|
||||
#include <random>
|
||||
#include <algorithm>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
#include "libslic3r/AABBTreeLines.hpp"
|
||||
#include "libslic3r/KDTreeIndirect.hpp"
|
||||
@@ -19,10 +44,12 @@
|
||||
#include "libslic3r/Layer.hpp"
|
||||
|
||||
#include "libslic3r/Geometry/Curves.hpp"
|
||||
#include "libslic3r/I18N.hpp"
|
||||
#include "libslic3r/ShortEdgeCollapse.hpp"
|
||||
#include "libslic3r/TriangleSetSampling.hpp"
|
||||
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "PreciseSeam.hpp"
|
||||
|
||||
//#define DEBUG_FILES
|
||||
|
||||
@@ -303,6 +330,14 @@ struct GlobalModelInfo {
|
||||
AABBTreeIndirect::Tree<3, float> enforcers_tree;
|
||||
AABBTreeIndirect::Tree<3, float> blockers_tree;
|
||||
|
||||
// Precise Seam modifiers: strong modifiers (CENTER/LEFT/RIGHT) determine exact seam placement
|
||||
std::vector<const ModelVolume*> precise_seam_strong_volumes;
|
||||
// Precise Seam modifiers: weak modifiers (ENFORCED/BLOCKED/NEUTRAL) provide hints for seam placement
|
||||
std::vector<const ModelVolume*> precise_seam_weak_volumes;
|
||||
|
||||
// Slice each modifier once; both consumers share structured regions and source provenance.
|
||||
PreciseSeam::ModifierRegionsCache precise_seam_slices;
|
||||
|
||||
bool is_enforced(const Vec3f &position, float radius) const {
|
||||
if (enforcers.empty()) {
|
||||
return false;
|
||||
@@ -403,7 +438,8 @@ struct GlobalModelInfo {
|
||||
;
|
||||
|
||||
//Extract perimeter polygons of the given layer
|
||||
Polygons extract_perimeter_polygons(const Layer *layer, std::vector<const LayerRegion*> &corresponding_regions_out) {
|
||||
Polygons extract_perimeter_polygons(const Layer *layer, std::vector<const LayerRegion*> &corresponding_regions_out,
|
||||
bool has_precise_seam_modifiers) {
|
||||
Polygons polygons;
|
||||
for (const LayerRegion *layer_region : layer->regions()) {
|
||||
for (const ExtrusionEntity *ex_entity : layer_region->perimeters.entities) {
|
||||
@@ -440,6 +476,18 @@ Polygons extract_perimeter_polygons(const Layer *layer, std::vector<const LayerR
|
||||
}
|
||||
}
|
||||
|
||||
if (has_precise_seam_modifiers) {
|
||||
// Extrusion loops repeat their start point; Polygon closes the contour implicitly.
|
||||
// Normalize here for Precise Seam without changing ordinary seam candidates.
|
||||
for (Polygon &polygon : polygons) {
|
||||
// Adjacent extrusion paths share endpoints; zero-length edges would prevent refinement at their junctions.
|
||||
// Remove only consecutive duplicates, preserving distinct visits to a self-touching contour point.
|
||||
polygon.points.erase(std::unique(polygon.points.begin(), polygon.points.end()), polygon.points.end());
|
||||
while (polygon.size() > 1 && polygon.points.front() == polygon.points.back())
|
||||
polygon.points.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
if (polygons.empty()) { // If there are no perimeter polygons for whatever reason (disabled perimeters .. ) insert dummy point
|
||||
// it is easier than checking everywhere if the layer is not emtpy, no seam will be placed to this layer anyway
|
||||
polygons.emplace_back(Points{ { 0, 0 } });
|
||||
@@ -449,17 +497,46 @@ Polygons extract_perimeter_polygons(const Layer *layer, std::vector<const LayerR
|
||||
return polygons;
|
||||
}
|
||||
|
||||
// Insert SeamCandidates created from perimeter polygons in to the result vector.
|
||||
// Compute its type (Enfrocer,Blocker), angle, and position
|
||||
//each SeamCandidate also contains pointer to shared Perimeter structure representing the polygon
|
||||
// if Custom Seam modifiers are present, oversamples the polygon if necessary to better fit user intentions
|
||||
// Build SeamCandidates for each vertex of the perimeter polygon and attach them to a shared Perimeter.
|
||||
// For each vertex: computes position, angle, and type (Enforcer / Blocker / Neutral).
|
||||
// When Precise Seam modifiers are present: inserts strong seam point,
|
||||
// oversamples enforcer edges, applies weak modifiers, marks one enforced point as central for alignment.
|
||||
void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, const LayerRegion *region,
|
||||
const GlobalModelInfo &global_model_info, PrintObjectSeamData::LayerSeams &result) {
|
||||
const GlobalModelInfo &global_model_info, PrintObjectSeamData::LayerSeams &result,
|
||||
PreciseSeam::PreciseSeamWarnings* warnings = nullptr) {
|
||||
if (orig_polygon.size() == 0) {
|
||||
return;
|
||||
}
|
||||
Polygon polygon = orig_polygon;
|
||||
bool was_clockwise = polygon.make_counter_clockwise();
|
||||
|
||||
// Process Precise Seam modifiers to find seam placement
|
||||
const Layer* layer = region ? region->layer() : nullptr;
|
||||
|
||||
// Use pre-computed Precise Seam volumes from global_model_info (computed once in init)
|
||||
const auto& strong_volumes = global_model_info.precise_seam_strong_volumes;
|
||||
const auto& weak_volumes = global_model_info.precise_seam_weak_volumes;
|
||||
|
||||
std::optional<Point> seam_point;
|
||||
std::vector<PreciseSeam::WeakModifierSegment> weak_segments;
|
||||
if (layer != nullptr && (!strong_volumes.empty() || !weak_volumes.empty())) {
|
||||
// Share validation, bounds and clipping line across all modifiers while the polygon is unchanged.
|
||||
// A strong insertion ends processing; otherwise weak reads the same preparation before inserting.
|
||||
const PreciseSeam::PreparedPerimeter prepared(polygon);
|
||||
seam_point = PreciseSeam::insert_strong_seam_point(
|
||||
strong_volumes, polygon, prepared, layer, global_model_info.precise_seam_slices, warnings);
|
||||
if (!seam_point.has_value())
|
||||
weak_segments = PreciseSeam::collect_weak_modifier_segments(
|
||||
weak_volumes, polygon, prepared, layer, global_model_info.precise_seam_slices, warnings);
|
||||
}
|
||||
|
||||
// Store the inserted point position for marking as central_enforcer later.
|
||||
std::optional<Vec3f> inserted_seam_position;
|
||||
if (seam_point.has_value()) {
|
||||
Vec2f unscaled_p = unscale(seam_point.value()).cast<float>();
|
||||
inserted_seam_position = Vec3f(unscaled_p.x(), unscaled_p.y(), z_coord);
|
||||
}
|
||||
|
||||
float angle_arm_len = region != nullptr ? region->flow(FlowRole::frExternalPerimeter).nozzle_diameter() : 0.5f;
|
||||
|
||||
std::vector<float> lengths { };
|
||||
@@ -528,10 +605,15 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, const
|
||||
|
||||
perimeter.end_index = result.points.size();
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
if (some_point_enforced) {
|
||||
// We will patches of enforced points (patch: continuous section of enforced points), choose
|
||||
// the longest patch, and select the middle point or sharp point (depending on the angle)
|
||||
// this point will have high priority on this perimeter
|
||||
// Choose the continuous enforced patch with the most candidates, then select its middle
|
||||
// candidate or a sharp corner. Patch length here is a point count, not geometric distance.
|
||||
size_t perimeter_size = perimeter.end_index - perimeter.start_index;
|
||||
const auto next_index = [&](size_t idx) {
|
||||
return perimeter.start_index + Slic3r::next_idx_modulo(idx - perimeter.start_index, perimeter_size);
|
||||
@@ -548,7 +630,8 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, const
|
||||
patches_starts_ends.push_back(next_index(i));
|
||||
}
|
||||
}
|
||||
//if patches_starts_ends are empty, it means that the whole perimeter is enforced.. don't do anything in that case
|
||||
// If patches_starts_ends are empty, the whole perimeter is enforced, or no point is enforced any more
|
||||
// (Precise Seam weak zones retyped every painted enforcer); don't do anything in either case.
|
||||
if (!patches_starts_ends.empty()) {
|
||||
//if the first point in the patches is not enforced, it marks a patch end. in that case, put it to the end and start on next
|
||||
// to simplify the processing
|
||||
@@ -562,7 +645,9 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, const
|
||||
std::pair<size_t, size_t> longest_patch { 0, 0 };
|
||||
auto patch_len = [perimeter_size](const std::pair<size_t, size_t> &start_end) {
|
||||
if (start_end.second < start_end.first) {
|
||||
return start_end.first + (perimeter_size - start_end.second);
|
||||
// Count [start, end) across the closing edge, independently of the contour's start.
|
||||
// Subtract indices first: they are offsets in the layer, not local perimeter indices.
|
||||
return perimeter_size - (start_end.first - start_end.second);
|
||||
} else {
|
||||
return start_end.second - start_end.first;
|
||||
}
|
||||
@@ -595,30 +680,24 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, const
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Get index of previous and next perimeter point of the layer. Because SeamCandidates of all polygons of the given layer
|
||||
// are sequentially stored in the vector, each perimeter contains info about start and end index. These vales are used to
|
||||
// deduce index of previous and next neigbour in the corresponding perimeter.
|
||||
std::pair<size_t, size_t> find_previous_and_next_perimeter_point(const std::vector<SeamCandidate> &perimeter_points,
|
||||
size_t point_index) {
|
||||
const SeamCandidate ¤t = perimeter_points[point_index];
|
||||
int prev = point_index - 1; //for majority of points, it is true that neighbours lie behind and in front of them in the vector
|
||||
int next = point_index + 1;
|
||||
|
||||
if (point_index == current.perimeter.start_index) {
|
||||
// if point_index is equal to start, it means that the previous neighbour is at the end
|
||||
prev = current.perimeter.end_index;
|
||||
// Apply precise seam point if it was inserted
|
||||
if (inserted_seam_position.has_value()) {
|
||||
// Set single point as Enforced, block all others
|
||||
for (size_t i = perimeter.start_index; i < perimeter.end_index; ++i) {
|
||||
if (result.points[i].position == inserted_seam_position.value()) {
|
||||
// Mark as the single enforced point with highest priority
|
||||
result.points[i].type = EnforcedBlockedSeamPoint::Enforced;
|
||||
result.points[i].central_enforcer = true;
|
||||
perimeter.precise_seam_point = inserted_seam_position;
|
||||
perimeter.precise_seam_index = i;
|
||||
} else {
|
||||
// Block all other points
|
||||
result.points[i].type = EnforcedBlockedSeamPoint::Blocked;
|
||||
result.points[i].central_enforcer = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (point_index == current.perimeter.end_index - 1) {
|
||||
// if point_index is equal to end, than next neighbour is at the start
|
||||
next = current.perimeter.start_index;
|
||||
}
|
||||
|
||||
assert(prev >= 0);
|
||||
assert(next >= 0);
|
||||
return {size_t(prev),size_t(next)};
|
||||
}
|
||||
|
||||
// Computes all global model info - transforms object, performs raycasting
|
||||
@@ -636,7 +715,9 @@ void compute_global_occlusion(GlobalModelInfo &result, const PrintObject *po,
|
||||
|| model_volume->type() == ModelVolumeType::NEGATIVE_VOLUME) {
|
||||
auto model_transformation = model_volume->get_matrix();
|
||||
indexed_triangle_set model_its = model_volume->mesh().its;
|
||||
// ORCA: Mirrored transforms flip winding, keep normals outward
|
||||
// ORCA fix (not related to Precise Seam, discovered during its development):
|
||||
// Mirror transforms have negative determinant which flips triangle winding.
|
||||
// fix_left_handed=true swaps indices to keep normals pointing outward.
|
||||
its_transform(model_its, model_transformation, true);
|
||||
if (model_volume->type() == ModelVolumeType::MODEL_PART) {
|
||||
its_merge(triangle_set, model_its);
|
||||
@@ -657,7 +738,10 @@ void compute_global_occlusion(GlobalModelInfo &result, const PrintObject *po,
|
||||
|
||||
size_t negative_volumes_start_index = triangle_set.indices.size();
|
||||
its_merge(triangle_set, negative_volumes_set);
|
||||
// ORCA: Mirroring flips normals, keep them outward for visibility sampling
|
||||
// ORCA fix (not related to Precise Seam, discovered during its development):
|
||||
// Object-level transform may include mirroring (negative determinant),
|
||||
// which inverts triangle winding. fix_left_handed=true corrects this
|
||||
// so visibility ray sampling sees outward-facing normals.
|
||||
its_transform(triangle_set, obj_transform, true);
|
||||
BOOST_LOG_TRIVIAL(debug)
|
||||
<< "SeamPlacer: decimate: end";
|
||||
@@ -715,16 +799,22 @@ void gather_enforcers_blockers(GlobalModelInfo &result, const PrintObject *po) {
|
||||
auto obj_transform = po->trafo_sliced();
|
||||
|
||||
for (const ModelVolume *mv : po->model_object()->volumes) {
|
||||
// Collect painting only from model parts (what the gizmo edits) and negative volumes (the only way
|
||||
// to paint a hole's wall); painting left on modifiers and helpers after a type change is ignored.
|
||||
// TODO: painting on negative volumes still affects the seam, but the gizmo neither shows nor edits it;
|
||||
// making it editable also needs model_custom_seam_data_changed() to track it.
|
||||
if (!mv->is_model_part() && !mv->is_negative_volume())
|
||||
continue;
|
||||
if (mv->is_seam_painted()) {
|
||||
auto model_transformation = obj_transform * mv->get_matrix();
|
||||
|
||||
indexed_triangle_set enforcers = mv->seam_facets.get_facets(*mv, EnforcerBlockerType::ENFORCER);
|
||||
// ORCA: Keep normals outward when mirroring seam enforcers
|
||||
// ORCA fix (not related to Precise Seam): fix winding for mirrored transforms
|
||||
its_transform(enforcers, model_transformation, true);
|
||||
its_merge(result.enforcers, enforcers);
|
||||
|
||||
indexed_triangle_set blockers = mv->seam_facets.get_facets(*mv, EnforcerBlockerType::BLOCKER);
|
||||
// ORCA: Keep normals outward when mirroring seam blockers
|
||||
// ORCA fix (not related to Precise Seam): fix winding for mirrored transforms
|
||||
its_transform(blockers, model_transformation, true);
|
||||
its_merge(result.blockers, blockers);
|
||||
}
|
||||
@@ -1011,13 +1101,14 @@ void pick_random_seam_point(const std::vector<SeamCandidate> &perimeter_points,
|
||||
// Parallel process and extract each perimeter polygon of the given print object.
|
||||
// Gather SeamCandidates of each layer into vector and build KDtree over them
|
||||
// Store results in the SeamPlacer variables m_seam_per_object
|
||||
void SeamPlacer::gather_seam_candidates(const PrintObject *po, const SeamPlacerImpl::GlobalModelInfo &global_model_info) {
|
||||
void SeamPlacer::gather_seam_candidates(const PrintObject *po, const SeamPlacerImpl::GlobalModelInfo &global_model_info,
|
||||
PreciseSeam::PreciseSeamWarnings* warnings) {
|
||||
using namespace SeamPlacerImpl;
|
||||
PrintObjectSeamData &seam_data = m_seam_per_object.emplace(po, PrintObjectSeamData { }).first->second;
|
||||
seam_data.layers.resize(po->layer_count());
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, po->layers().size()),
|
||||
[po, &global_model_info, &seam_data]
|
||||
[po, &global_model_info, &seam_data, warnings]
|
||||
(tbb::blocked_range<size_t> r) {
|
||||
for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) {
|
||||
PrintObjectSeamData::LayerSeams &layer_seams = seam_data.layers[layer_idx];
|
||||
@@ -1025,10 +1116,13 @@ void SeamPlacer::gather_seam_candidates(const PrintObject *po, const SeamPlacerI
|
||||
auto unscaled_z = layer->slice_z;
|
||||
std::vector<const LayerRegion*> regions;
|
||||
//NOTE corresponding region ptr may be null, if the layer has zero perimeters
|
||||
Polygons polygons = extract_perimeter_polygons(layer, regions);
|
||||
const bool has_precise_seam_modifiers = !global_model_info.precise_seam_strong_volumes.empty() ||
|
||||
!global_model_info.precise_seam_weak_volumes.empty();
|
||||
Polygons polygons = extract_perimeter_polygons(layer, regions, has_precise_seam_modifiers);
|
||||
for (size_t poly_index = 0; poly_index < polygons.size(); ++poly_index) {
|
||||
process_perimeter_polygon(polygons[poly_index], unscaled_z,
|
||||
regions[poly_index], global_model_info, layer_seams);
|
||||
regions[poly_index], global_model_info, layer_seams,
|
||||
warnings);
|
||||
}
|
||||
auto functor = SeamCandidateCoordinateFunctor { layer_seams.points };
|
||||
seam_data.layers[layer_idx].points_tree =
|
||||
@@ -1427,6 +1521,10 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl::
|
||||
void SeamPlacer::init(const Print &print, std::function<void(void)> throw_if_canceled_func) {
|
||||
using namespace SeamPlacerImpl;
|
||||
m_seam_per_object.clear();
|
||||
m_precise_seam_warning.clear();
|
||||
|
||||
// Warning flags for Precise Seam processing — shared across all objects
|
||||
PreciseSeam::PreciseSeamWarnings precise_seam_warnings;
|
||||
|
||||
for (const PrintObject *po : print.objects()) {
|
||||
throw_if_canceled_func();
|
||||
@@ -1436,14 +1534,33 @@ void SeamPlacer::init(const Print &print, std::function<void(void)> throw_if_can
|
||||
{
|
||||
GlobalModelInfo global_model_info { };
|
||||
gather_enforcers_blockers(global_model_info, po);
|
||||
PreciseSeam::init_precise_seam_data(
|
||||
global_model_info.precise_seam_strong_volumes,
|
||||
global_model_info.precise_seam_weak_volumes,
|
||||
m_seam_per_object[po].has_precise_seam_strong_volumes,
|
||||
po->model_object());
|
||||
|
||||
// Slice each Precise Seam modifier once per object; both consumers read the cache.
|
||||
for (const ModelVolume* vol : global_model_info.precise_seam_strong_volumes)
|
||||
global_model_info.precise_seam_slices[vol] = PreciseSeam::prepare_modifier_slices(po->slice_single_volume_regions(vol));
|
||||
for (const ModelVolume* vol : global_model_info.precise_seam_weak_volumes)
|
||||
global_model_info.precise_seam_slices[vol] = PreciseSeam::prepare_modifier_slices(po->slice_single_volume_regions(vol));
|
||||
// Register usage tracking before the parallel phase; workers only set its flags. Several print
|
||||
// objects of one model object share volumes, and try_emplace keeps what earlier ones recorded.
|
||||
for (const ModelVolume* vol : global_model_info.precise_seam_strong_volumes)
|
||||
precise_seam_warnings.modifier_usage.try_emplace(vol);
|
||||
for (const ModelVolume* vol : global_model_info.precise_seam_weak_volumes)
|
||||
precise_seam_warnings.modifier_usage.try_emplace(vol);
|
||||
|
||||
throw_if_canceled_func();
|
||||
if (configured_seam_preference == spAligned || configured_seam_preference == spNearest || configured_seam_preference == spAlignedBack) {
|
||||
compute_global_occlusion(global_model_info, po, throw_if_canceled_func, configured_seam_preference);
|
||||
}
|
||||
throw_if_canceled_func();
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug)
|
||||
<< "SeamPlacer: gather_seam_candidates: start";
|
||||
gather_seam_candidates(po, global_model_info);
|
||||
gather_seam_candidates(po, global_model_info, &precise_seam_warnings);
|
||||
BOOST_LOG_TRIVIAL(debug)
|
||||
<< "SeamPlacer: gather_seam_candidates: end";
|
||||
throw_if_canceled_func();
|
||||
@@ -1491,10 +1608,96 @@ void SeamPlacer::init(const Print &print, std::function<void(void)> throw_if_can
|
||||
<< "SeamPlacer: align_seam_points : end";
|
||||
}
|
||||
|
||||
// Restore precise seam positions that were potentially modified
|
||||
if (m_seam_per_object[po].has_precise_seam_strong_volumes) {
|
||||
PreciseSeam::restore_precise_seam_positions(m_seam_per_object[po].layers);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_FILES
|
||||
debug_export_points(m_seam_per_object[po].layers, po->bounding_box(), comparator);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Prepare one combined Precise Seam warning; G-code export issues it. Keep it single: separate
|
||||
// warnings would each re-push all warnings and duplicate text in the notification.
|
||||
{
|
||||
const unsigned failed_types = precise_seam_warnings.failed_types.load(std::memory_order_relaxed);
|
||||
const unsigned mi = precise_seam_warnings.multiple_intersections.load(std::memory_order_relaxed);
|
||||
const unsigned fc = precise_seam_warnings.full_containment.load(std::memory_order_relaxed);
|
||||
const size_t failed = precise_seam_warnings.failed_fragments.load(std::memory_order_relaxed);
|
||||
// All workers have finished; cancellation before this point may omit the summary.
|
||||
if (failed > PreciseSeam::failed_fragment_log_limit)
|
||||
BOOST_LOG_TRIVIAL(warning) << "[PreciseSeamIntersectionFailed] " << failed
|
||||
<< " fragments discarded; first " << PreciseSeam::failed_fragment_log_limit
|
||||
<< " logged (parallel processing order), " << (failed - PreciseSeam::failed_fragment_log_limit)
|
||||
<< " omitted";
|
||||
// Recoveries are log-only: no user warning, but the same bounded detail and a total.
|
||||
const size_t recovered = precise_seam_warnings.recovered_fragments.load(std::memory_order_relaxed);
|
||||
if (recovered > PreciseSeam::failed_fragment_log_limit)
|
||||
BOOST_LOG_TRIVIAL(warning) << "[PreciseSeamFragmentRecovered] " << recovered
|
||||
<< " fragments recovered; first " << PreciseSeam::failed_fragment_log_limit
|
||||
<< " logged (parallel processing order), " << (recovered - PreciseSeam::failed_fragment_log_limit)
|
||||
<< " omitted";
|
||||
// Reasons name the modifier types, as the menu does, not individual modifiers: "Seam Left, Seam
|
||||
// Enforced" in menu order, each type once. The same msgids as the menu share its translations.
|
||||
const auto type_list = [](unsigned mask) {
|
||||
const std::pair<ModelVolumeType, std::string> types[] = {
|
||||
{ModelVolumeType::PRECISE_SEAM_CENTER, _u8L("Seam Center")},
|
||||
{ModelVolumeType::PRECISE_SEAM_LEFT, _u8L("Seam Left")},
|
||||
{ModelVolumeType::PRECISE_SEAM_RIGHT, _u8L("Seam Right")},
|
||||
{ModelVolumeType::PRECISE_SEAM_ENFORCED, _u8L("Seam Enforced")},
|
||||
{ModelVolumeType::PRECISE_SEAM_BLOCKED, _u8L("Seam Blocked")},
|
||||
{ModelVolumeType::PRECISE_SEAM_NEUTRAL, _u8L("Seam Neutral")}};
|
||||
std::string list;
|
||||
for (const auto &[type, name] : types)
|
||||
if (mask & PreciseSeam::PreciseSeamWarnings::type_bit(type))
|
||||
list += (list.empty() ? "" : ", ") + name;
|
||||
return list;
|
||||
};
|
||||
std::vector<std::string> parts;
|
||||
if (failed_types != 0)
|
||||
parts.push_back((boost::format(_u8L("failed to process some intersections (%1%)")) % type_list(failed_types)).str());
|
||||
if (mi != 0)
|
||||
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);
|
||||
}
|
||||
// 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
|
||||
<< "\" modifier=\"" << volume->name << "\"";
|
||||
if (!no_effect.empty()) {
|
||||
const ModelVolume *first = no_effect.front();
|
||||
if (no_effect.size() == 1)
|
||||
parts.push_back((boost::format(_u8L("modifier \"%1%\" of \"%2%\" had no effect on the seam (it might not reach the centerline of the printed perimeter)"))
|
||||
% first->name % first->get_object()->name).str());
|
||||
else
|
||||
parts.push_back((boost::format(_u8L("modifier \"%1%\" of \"%2%\" (%3% in total) had no effect on the seam (it might not reach the centerline of the printed perimeter)"))
|
||||
% first->name % first->get_object()->name % no_effect.size()).str());
|
||||
}
|
||||
if (!parts.empty()) {
|
||||
// One line: the export warnings dialog shows only the first line of each warning.
|
||||
std::string warning_text = _u8L("Precise Seam") + ": ";
|
||||
for (size_t i = 0; i < parts.size(); ++i) {
|
||||
if (i > 0) warning_text += "; ";
|
||||
warning_text += parts[i];
|
||||
}
|
||||
warning_text += ". ";
|
||||
warning_text += _u8L("Seam placement may differ from expected.");
|
||||
m_precise_seam_warning = std::move(warning_text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop,
|
||||
|
||||
Reference in New Issue
Block a user