diff --git a/src/libslic3r/GCode/SeamPainting.cpp b/src/libslic3r/GCode/SeamPainting.cpp index 1f78eb5098..1f029bc5e8 100644 --- a/src/libslic3r/GCode/SeamPainting.cpp +++ b/src/libslic3r/GCode/SeamPainting.cpp @@ -7,12 +7,12 @@ Painting::Painting(const Transform3d &obj_transform, const ModelVolumePtrs &volu auto model_transformation = obj_transform * mv->get_matrix(); indexed_triangle_set enforcers = mv->seam_facets - .get_facets(*mv, EnforcerBlockerType::ENFORCER); + .get_facets(*mv, TriangleStateType::ENFORCER); its_transform(enforcers, model_transformation); its_merge(this->enforcers, enforcers); indexed_triangle_set blockers = mv->seam_facets - .get_facets(*mv, EnforcerBlockerType::BLOCKER); + .get_facets(*mv, TriangleStateType::BLOCKER); its_transform(blockers, model_transformation); its_merge(this->blockers, blockers); } diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index fa984dcfbb..810d7ef4b9 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -637,6 +637,21 @@ Transform3d Transformation::get_matrix_no_scaling_factor() const return copy.get_matrix(); } +Transform3d Transformation::get_matrix_with_applied_shrinkage_compensation(const Vec3d &shrinkage_compensation) const { + const Transform3d shrinkage_trafo = Geometry::scale_transform(shrinkage_compensation); + const Vec3d trafo_offset = this->get_offset(); + const Vec3d trafo_offset_xy = Vec3d(trafo_offset.x(), trafo_offset.y(), 0.); + + Transformation copy(*this); + copy.set_offset(Axis::X, 0.); + copy.set_offset(Axis::Y, 0.); + + Transform3d trafo_after_shrinkage = (shrinkage_trafo * copy.get_matrix()); + trafo_after_shrinkage.translation() += trafo_offset_xy; + + return trafo_after_shrinkage; +} + Transformation Transformation::operator * (const Transformation& other) const { return Transformation(get_matrix() * other.get_matrix()); diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index c5d81bda54..3afe6923da 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -460,6 +460,8 @@ public: Transform3d get_matrix_no_offset() const; Transform3d get_matrix_no_scaling_factor() const; + Transform3d get_matrix_with_applied_shrinkage_compensation(const Vec3d &shrinkage_compensation) const; + void set_matrix(const Transform3d& transform) { m_matrix = transform; } Transformation operator * (const Transformation& other) const; diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 52ef00594c..2de86a5283 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1998,6 +1998,22 @@ void ModelVolume::convert_from_meters() this->source.is_converted_from_meters = true; } +std::vector ModelVolume::get_extruders_from_multi_material_painting() const { + if (!this->is_mm_painted()) + return {}; + + assert(static_cast(TriangleStateType::Extruder1) - 1 == 0); + const TriangleSelector::TriangleSplittingData &data = this->mm_segmentation_facets.get_data(); + + std::vector extruders; + for (size_t state_idx = static_cast(TriangleStateType::Extruder1); state_idx < data.used_states.size(); ++state_idx) { + if (data.used_states[state_idx]) + extruders.emplace_back(state_idx - 1); + } + + return extruders; +} + void ModelInstance::transform_mesh(TriangleMesh* mesh, bool dont_translate) const { mesh->transform(dont_translate ? get_matrix_no_offset() : get_matrix()); @@ -2021,7 +2037,7 @@ void ModelInstance::transform_polygon(Polygon* polygon) const polygon->scale(get_scaling_factor(X), get_scaling_factor(Y)); // scale around polygon origin } -indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, EnforcerBlockerType type) const +indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, TriangleStateType type) const { TriangleSelector selector(mv.mesh()); // Reset of TriangleSelector is done inside TriangleSelector's constructor, so we don't need it to perform it again in deserialize(). @@ -2029,7 +2045,7 @@ indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, Enforce return selector.get_facets(type); } -indexed_triangle_set FacetsAnnotation::get_facets_strict(const ModelVolume& mv, EnforcerBlockerType type) const +indexed_triangle_set FacetsAnnotation::get_facets_strict(const ModelVolume& mv, TriangleStateType type) const { TriangleSelector selector(mv.mesh()); // Reset of TriangleSelector is done inside TriangleSelector's constructor, so we don't need it to perform it again in deserialize(). @@ -2037,14 +2053,14 @@ indexed_triangle_set FacetsAnnotation::get_facets_strict(const ModelVolume& mv, return selector.get_facets_strict(type); } -bool FacetsAnnotation::has_facets(const ModelVolume& mv, EnforcerBlockerType type) const +bool FacetsAnnotation::has_facets(const ModelVolume& mv, TriangleStateType type) const { return TriangleSelector::has_facets(m_data, type); } bool FacetsAnnotation::set(const TriangleSelector& selector) { - std::pair>, std::vector> sel_map = selector.serialize(); + TriangleSelector::TriangleSplittingData sel_map = selector.serialize(); if (sel_map != m_data) { m_data = std::move(sel_map); this->touch(); @@ -2055,8 +2071,8 @@ bool FacetsAnnotation::set(const TriangleSelector& selector) void FacetsAnnotation::reset() { - m_data.first.clear(); - m_data.second.clear(); + m_data.triangles_to_split.clear(); + m_data.bitstream.clear(); this->touch(); } @@ -2067,15 +2083,15 @@ std::string FacetsAnnotation::get_triangle_as_string(int triangle_idx) const { std::string out; - auto triangle_it = std::lower_bound(m_data.first.begin(), m_data.first.end(), triangle_idx, [](const std::pair &l, const int r) { return l.first < r; }); - if (triangle_it != m_data.first.end() && triangle_it->first == triangle_idx) { - int offset = triangle_it->second; - int end = ++ triangle_it == m_data.first.end() ? int(m_data.second.size()) : triangle_it->second; + auto triangle_it = std::lower_bound(m_data.triangles_to_split.begin(), m_data.triangles_to_split.end(), triangle_idx, [](const TriangleSelector::TriangleBitStreamMapping &l, const int r) { return l.triangle_idx < r; }); + if (triangle_it != m_data.triangles_to_split.end() && triangle_it->triangle_idx == triangle_idx) { + int offset = triangle_it->bitstream_start_idx; + int end = ++ triangle_it == m_data.triangles_to_split.end() ? int(m_data.bitstream.size()) : triangle_it->bitstream_start_idx; while (offset < end) { int next_code = 0; for (int i=3; i>=0; --i) { next_code = next_code << 1; - next_code |= int(m_data.second[offset + i]); + next_code |= int(m_data.bitstream[offset + i]); } offset += 4; @@ -2092,9 +2108,10 @@ std::string FacetsAnnotation::get_triangle_as_string(int triangle_idx) const void FacetsAnnotation::set_triangle_from_string(int triangle_id, const std::string& str) { assert(! str.empty()); - assert(m_data.first.empty() || m_data.first.back().first < triangle_id); - m_data.first.emplace_back(triangle_id, int(m_data.second.size())); + assert(m_data.triangles_to_split.empty() || m_data.triangles_to_split.back().triangle_idx < triangle_id); + m_data.triangles_to_split.emplace_back(triangle_id, int(m_data.bitstream.size())); + const size_t bitstream_start_idx = m_data.bitstream.size(); for (auto it = str.crbegin(); it != str.crend(); ++it) { const char ch = *it; int dec = 0; @@ -2106,9 +2123,11 @@ void FacetsAnnotation::set_triangle_from_string(int triangle_id, const std::stri assert(false); // Convert to binary and append into code. - for (int i=0; i<4; ++i) - m_data.second.insert(m_data.second.end(), bool(dec & (1 << i))); + for (int i = 0; i < 4; ++i) + m_data.bitstream.insert(m_data.bitstream.end(), bool(dec & (1 << i))); } + + m_data.update_used_states(bitstream_start_idx); } // Test whether the two models contain the same number of ModelObjects with the same set of IDs diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index c885b78e56..7ef98db151 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -27,6 +27,7 @@ #include "enum_bitmask.hpp" #include "TextConfiguration.hpp" #include "EmbossShape.hpp" +#include "TriangleSelector.hpp" #include #include @@ -56,7 +57,6 @@ class ModelVolume; class ModelWipeTower; class Print; class SLAPrint; -class TriangleSelector; namespace UndoRedo { class StackImpl; @@ -654,29 +654,6 @@ private: void update_min_max_z(); }; -enum class EnforcerBlockerType : int8_t { - // Maximum is 3. The value is serialized in TriangleSelector into 2 bits. - NONE = 0, - ENFORCER = 1, - BLOCKER = 2, - // Maximum is 15. The value is serialized in TriangleSelector into 6 bits using a 2 bit prefix code. - Extruder1 = ENFORCER, - Extruder2 = BLOCKER, - Extruder3, - Extruder4, - Extruder5, - Extruder6, - Extruder7, - Extruder8, - Extruder9, - Extruder10, - Extruder11, - Extruder12, - Extruder13, - Extruder14, - Extruder15, -}; - enum class ConversionType : int { CONV_TO_INCH, CONV_FROM_INCH, @@ -687,14 +664,14 @@ enum class ConversionType : int { class FacetsAnnotation final : public ObjectWithTimestamp { public: // Assign the content if the timestamp differs, don't assign an ObjectID. - void assign(const FacetsAnnotation& rhs) { if (! this->timestamp_matches(rhs)) { m_data = rhs.m_data; this->copy_timestamp(rhs); } } - void assign(FacetsAnnotation&& rhs) { if (! this->timestamp_matches(rhs)) { m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } } - const std::pair>, std::vector>& get_data() const throw() { return m_data; } + void assign(const FacetsAnnotation &rhs) { if (! this->timestamp_matches(rhs)) { m_data = rhs.m_data; this->copy_timestamp(rhs); } } + void assign(FacetsAnnotation &&rhs) { if (! this->timestamp_matches(rhs)) { m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } } + const TriangleSelector::TriangleSplittingData &get_data() const noexcept { return m_data; } bool set(const TriangleSelector& selector); - indexed_triangle_set get_facets(const ModelVolume& mv, EnforcerBlockerType type) const; - indexed_triangle_set get_facets_strict(const ModelVolume& mv, EnforcerBlockerType type) const; - bool has_facets(const ModelVolume& mv, EnforcerBlockerType type) const; - bool empty() const { return m_data.first.empty(); } + indexed_triangle_set get_facets(const ModelVolume& mv, TriangleStateType type) const; + indexed_triangle_set get_facets_strict(const ModelVolume& mv, TriangleStateType type) const; + bool has_facets(const ModelVolume& mv, TriangleStateType type) const; + bool empty() const { return m_data.triangles_to_split.empty(); } // Following method clears the config and increases its timestamp, so the deleted // state is considered changed from perspective of the undo/redo stack. @@ -704,11 +681,11 @@ public: std::string get_triangle_as_string(int i) const; // Before deserialization, reserve space for n_triangles. - void reserve(int n_triangles) { m_data.first.reserve(n_triangles); } + void reserve(int n_triangles) { m_data.triangles_to_split.reserve(n_triangles); } // Deserialize triangles one by one, with strictly increasing triangle_id. void set_triangle_from_string(int triangle_id, const std::string& str); // After deserializing the last triangle, shrink data to fit. - void shrink_to_fit() { m_data.first.shrink_to_fit(); m_data.second.shrink_to_fit(); } + void shrink_to_fit() { m_data.triangles_to_split.shrink_to_fit(); m_data.bitstream.shrink_to_fit(); } private: // Constructors to be only called by derived classes. @@ -718,9 +695,9 @@ private: // by an existing ID copied from elsewhere. explicit FacetsAnnotation(int) : ObjectWithTimestamp(-1) {} // Copy constructor copies the ID. - explicit FacetsAnnotation(const FacetsAnnotation &rhs) = default; + FacetsAnnotation(const FacetsAnnotation &rhs) = default; // Move constructor copies the ID. - explicit FacetsAnnotation(FacetsAnnotation &&rhs) = default; + FacetsAnnotation(FacetsAnnotation &&rhs) = default; // called by ModelVolume::assign_copy() FacetsAnnotation& operator=(const FacetsAnnotation &rhs) = default; @@ -729,12 +706,9 @@ private: friend class cereal::access; friend class UndoRedo::StackImpl; - template void serialize(Archive &ar) - { - ar(cereal::base_class(this), m_data); - } + template void serialize(Archive &ar) { ar(cereal::base_class(this), m_data); } - std::pair>, std::vector> m_data; + TriangleSelector::TriangleSplittingData m_data; // To access set_new_unique_id() when copy / pasting a ModelVolume. friend class ModelVolume; @@ -936,6 +910,9 @@ public: bool is_seam_painted() const { return !this->seam_facets.empty(); } bool is_mm_painted() const { return !this->mm_segmentation_facets.empty(); } + // Returns 0-based indices of extruders painted by multi-material painting gizmo. + std::vector get_extruders_from_multi_material_painting() const; + protected: friend class Print; friend class SLAPrint; diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index 708bdcf5ee..155f26e13d 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -894,7 +894,7 @@ static inline std::vector> mm_segmentation_top_and_botto if (mv->is_model_part()) { const Transform3d volume_trafo = object_trafo * mv->get_matrix(); for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++ extruder_idx) { - const indexed_triangle_set painted = mv->mm_segmentation_facets.get_facets_strict(*mv, EnforcerBlockerType(extruder_idx)); + const indexed_triangle_set painted = mv->mm_segmentation_facets.get_facets_strict(*mv, TriangleStateType(extruder_idx)); #ifdef MM_SEGMENTATION_DEBUG_TOP_BOTTOM { static int iRun = 0; @@ -1298,7 +1298,7 @@ std::vector> multi_material_segmentation_by_painting(con tbb::parallel_for(tbb::blocked_range(1, num_extruders + 1), [&mv, &print_object, &layers, &edge_grids, &painted_lines, &painted_lines_mutex, &input_expolygons, &throw_on_cancel_callback](const tbb::blocked_range &range) { for (size_t extruder_idx = range.begin(); extruder_idx < range.end(); ++extruder_idx) { throw_on_cancel_callback(); - const indexed_triangle_set custom_facets = mv->mm_segmentation_facets.get_facets(*mv, EnforcerBlockerType(extruder_idx)); + const indexed_triangle_set custom_facets = mv->mm_segmentation_facets.get_facets(*mv, TriangleStateType(extruder_idx)); if (!mv->is_model_part() || custom_facets.indices.empty()) continue; diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 046334058e..6e34c514d6 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -490,7 +490,9 @@ static std::vector s_Preset_filament_options { "filament_retract_layer_change", "filament_wipe", "filament_retract_before_wipe", "filament_retract_length_toolchange", "filament_retract_restart_extra_toolchange", "filament_travel_ramping_lift", "filament_travel_slope", "filament_travel_max_lift", "filament_travel_lift_before_obstacle", // Profile compatibility - "filament_vendor", "compatible_prints", "compatible_prints_condition", "compatible_printers", "compatible_printers_condition", "inherits" + "filament_vendor", "compatible_prints", "compatible_prints_condition", "compatible_printers", "compatible_printers_condition", "inherits", + // Shrinkage compensation + "filament_shrinkage_compensation_xy", "filament_shrinkage_compensation_z", }; static std::vector s_Preset_machine_limits_options { diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index e7d223cbd0..ddf38250af 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -209,7 +209,9 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n // Spiral Vase forces different kind of slicing than the normal model: // In Spiral Vase mode, holes are closed and only the largest area contour is kept at each layer. // Therefore toggling the Spiral Vase on / off requires complete reslicing. - || opt_key == "spiral_vase") { + || opt_key == "spiral_vase" + || opt_key == "filament_shrinkage_compensation_xy" + || opt_key == "filament_shrinkage_compensation_z") { osteps.emplace_back(posSlice); } else if ( opt_key == "complete_objects" @@ -525,6 +527,9 @@ std::string Print::validate(std::vector* warnings) const goto DONE; } DONE:; + + if (!this->has_same_shrinkage_compensations()) + warnings->emplace_back("_FILAMENT_SHRINKAGE_DIFFER"); } if (m_objects.empty()) @@ -584,12 +589,19 @@ std::string Print::validate(std::vector* warnings) const //FIXME It is quite expensive to generate object layers just to get the print height! if (auto layers = generate_object_layers(print_object.slicing_parameters(), layer_height_profile(print_object_idx)); ! layers.empty() && layers.back() > this->config().max_print_height + EPSILON) { - return - // Test whether the last slicing plane is below or above the print volume. - 0.5 * (layers[layers.size() - 2] + layers.back()) > this->config().max_print_height + EPSILON ? - format(_u8L("The object %1% exceeds the maximum build volume height."), print_object.model_object()->name) : - format(_u8L("While the object %1% itself fits the build volume, its last layer exceeds the maximum build volume height."), print_object.model_object()->name) + - " " + _u8L("You might want to reduce the size of your model or change current print settings and retry."); + + const double shrinkage_compensation_z = this->shrinkage_compensation().z(); + if (shrinkage_compensation_z != 1. && layers.back() > (this->config().max_print_height / shrinkage_compensation_z + EPSILON)) { + // The object exceeds the maximum build volume height because of shrinkage compensation. + return format(_u8L("While the object %1% itself fits the build volume, it exceeds the maximum build volume height because of material shrinkage compensation."), print_object.model_object()->name); + } else if (0.5 * (layers[layers.size() - 2] + layers.back()) > this->config().max_print_height + EPSILON) { + // The last slicing plane is below the print volume. + return format(_u8L("The object %1% exceeds the maximum build volume height."), print_object.model_object()->name); + } else { + // The last slicing plane is above the print volume. + return format(_u8L("While the object %1% itself fits the build volume, its last layer exceeds the maximum build volume height."), print_object.model_object()->name) + + " " + _u8L("You might want to reduce the size of your model or change current print settings and retry."); + } } } @@ -760,7 +772,7 @@ std::string Print::validate(std::vector* warnings) const if (! object->has_support() && warnings) { for (const ModelVolume* mv : object->model_object()->volumes) { bool has_enforcers = mv->is_support_enforcer() || - (mv->is_model_part() && mv->supported_facets.has_facets(*mv, EnforcerBlockerType::ENFORCER)); + (mv->is_model_part() && mv->supported_facets.has_facets(*mv, TriangleStateType::ENFORCER)); if (has_enforcers) { warnings->emplace_back("_SUPPORTS_OFF"); break; @@ -1618,6 +1630,40 @@ std::string Print::output_filename(const std::string &filename_base) const return this->PrintBase::output_filename(output_filename_format, ".gcode", filename_base, &config); } +// Returns if all used filaments have same shrinkage compensations. +bool Print::has_same_shrinkage_compensations() const { + const std::vector extruders = this->extruders(); + if (extruders.empty()) + return false; + + const double filament_shrinkage_compensation_xy = m_config.filament_shrinkage_compensation_xy.get_at(extruders.front()); + const double filament_shrinkage_compensation_z = m_config.filament_shrinkage_compensation_z.get_at(extruders.front()); + + for (unsigned int extruder : extruders) { + if (filament_shrinkage_compensation_xy != m_config.filament_shrinkage_compensation_xy.get_at(extruder) || + filament_shrinkage_compensation_z != m_config.filament_shrinkage_compensation_z.get_at(extruder)) { + return false; + } + } + + return true; +} + +// Returns scaling for each axis representing shrinkage compensations in each axis. +Vec3d Print::shrinkage_compensation() const +{ + if (!this->has_same_shrinkage_compensations()) + return Vec3d::Ones(); + + const unsigned int first_extruder = this->extruders().front(); + const double xy_compensation_percent = std::clamp(m_config.filament_shrinkage_compensation_xy.get_at(first_extruder), -99., 99.); + const double z_compensation_percent = std::clamp(m_config.filament_shrinkage_compensation_z.get_at(first_extruder), -99., 99.); + const double xy_compensation = 100. / (100. - xy_compensation_percent); + const double z_compensation = 100. / (100. - z_compensation_percent); + + return { xy_compensation, xy_compensation, z_compensation }; +} + const std::string PrintStatistics::FilamentUsedG = "filament used [g]"; const std::string PrintStatistics::FilamentUsedGMask = "; filament used [g] ="; diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 712bb91c22..0da013da73 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -327,7 +327,7 @@ public: // The slicing parameters are dependent on various configuration values // (layer height, first layer height, raft settings, print nozzle diameter etc). const SlicingParameters& slicing_parameters() const { return m_slicing_params; } - static SlicingParameters slicing_parameters(const DynamicPrintConfig &full_config, const ModelObject &model_object, float object_max_z); + static SlicingParameters slicing_parameters(const DynamicPrintConfig &full_config, const ModelObject &model_object, float object_max_z, const Vec3d &object_shrinkage_compensation); size_t num_printing_regions() const throw() { return m_shared_regions->all_regions.size(); } const PrintRegion& printing_region(size_t idx) const throw() { return *m_shared_regions->all_regions[idx].get(); } @@ -353,7 +353,7 @@ public: std::vector slice_support_enforcers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_ENFORCER); } // Helpers to project custom facets on slices - void project_and_append_custom_facets(bool seam, EnforcerBlockerType type, std::vector& expolys) const; + void project_and_append_custom_facets(bool seam, TriangleStateType type, std::vector& expolys) const; private: // to be called from Print only. @@ -665,6 +665,12 @@ public: const Polygons& get_sequential_print_clearance_contours() const { return m_sequential_print_clearance_contours; } static bool sequential_print_horizontal_clearance_valid(const Print& print, Polygons* polygons = nullptr); + // Returns if all used filaments have same shrinkage compensations. + bool has_same_shrinkage_compensations() const; + + // Returns scaling for each axis representing shrinkage compensations in each axis. + Vec3d shrinkage_compensation() const; + protected: // Invalidates the step, and its depending steps in Print. bool invalidate_step(PrintStep step); diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index 1ac2c72304..57c20a6b4d 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -134,13 +134,14 @@ struct PrintObjectTrafoAndInstances }; // Generate a list of trafos and XY offsets for instances of a ModelObject -static std::vector print_objects_from_model_object(const ModelObject &model_object) +static std::vector print_objects_from_model_object(const ModelObject &model_object, const Vec3d &shrinkage_compensation) { std::set trafos; PrintObjectTrafoAndInstances trafo; for (ModelInstance *model_instance : model_object.instances) if (model_instance->is_printable()) { - trafo.trafo = model_instance->get_matrix(); + Geometry::Transformation model_instance_transformation = model_instance->get_transformation(); + trafo.trafo = model_instance_transformation.get_matrix_with_applied_shrinkage_compensation(shrinkage_compensation); auto shift = Point::new_scale(trafo.trafo.data()[12], trafo.trafo.data()[13]); // Reset the XY axes of the transformation. trafo.trafo.data()[12] = 0; @@ -1281,7 +1282,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ // Walk over all new model objects and check, whether there are matching PrintObjects. for (ModelObject *model_object : m_model.objects) { ModelObjectStatus &model_object_status = const_cast(model_object_status_db.reuse(*model_object)); - model_object_status.print_instances = print_objects_from_model_object(*model_object); + model_object_status.print_instances = print_objects_from_model_object(*model_object, this->shrinkage_compensation()); std::vector old; old.reserve(print_object_status_db.count(*model_object)); for (const PrintObjectStatus &print_object_status : print_object_status_db.get_range(*model_object)) @@ -1375,9 +1376,20 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ if (const auto &volumes = print_object.model_object()->volumes; num_extruders > 1 && std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume *v) { return ! v->mm_segmentation_facets.empty(); }) != volumes.end()) { - //FIXME be more specific! Don't enumerate extruders that are not used for painting! - painting_extruders.assign(num_extruders, 0); - std::iota(painting_extruders.begin(), painting_extruders.end(), 1); + + std::array(TriangleStateType::Count)> used_facet_states{}; + for (const ModelVolume *volume : volumes) { + const std::vector &volume_used_facet_states = volume->mm_segmentation_facets.get_data().used_states; + + assert(volume_used_facet_states.size() == used_facet_states.size()); + for (size_t state_idx = 0; state_idx < std::min(volume_used_facet_states.size(), used_facet_states.size()); ++state_idx) + used_facet_states[state_idx] |= volume_used_facet_states[state_idx]; + } + + for (size_t state_idx = static_cast(TriangleStateType::Extruder1); state_idx < used_facet_states.size(); ++state_idx) { + if (used_facet_states[state_idx]) + painting_extruders.emplace_back(state_idx); + } } if (model_object_status.print_object_regions_status == ModelObjectStatus::PrintObjectRegionsStatus::Valid) { // Verify that the trafo for regions & volume bounding boxes thus for regions is still applicable. diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 632a48fdcc..b14ff3e11b 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1332,6 +1332,28 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionString(L("(Unknown)"))); def->cli = ConfigOptionDef::nocli; + def = this->add("filament_shrinkage_compensation_xy", coPercents); + def->label = L("Shrinkage XY"); + def->tooltip = L("Enter your filament shrinkage percentages for the X and Y axes here to apply scaling of the object to " + "compensate for shrinkage in the X and Y axes. For example, if you measured 99mm instead of 100mm, " + "then you should put here 1%."); + def->sidetext = L("%"); + def->mode = comAdvanced; + def->min = -10.; + def->max = 10.; + def->set_default_value(new ConfigOptionPercents { 0 }); + + def = this->add("filament_shrinkage_compensation_z", coPercents); + def->label = L("Shrinkage Z"); + def->tooltip = L("Enter your filament shrinkage percentages for the Z axis here to apply scaling of the object to " + "compensate for shrinkage in the Z axis. For example, if you measured 99mm instead of 100mm, " + "then you should put here 1%."); + def->sidetext = L("%"); + def->mode = comAdvanced; + def->min = -10.; + def->max = 10.; + def->set_default_value(new ConfigOptionPercents { 0. }); + def = this->add("fill_angle", coFloat); def->label = L("Fill angle"); def->category = L("Infill"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 72ba652e5f..e95f486776 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -745,6 +745,8 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloats, filament_multitool_ramming_flow)) ((ConfigOptionFloats, filament_stamping_loading_speed)) ((ConfigOptionFloats, filament_stamping_distance)) + ((ConfigOptionPercents, filament_shrinkage_compensation_xy)) + ((ConfigOptionPercents, filament_shrinkage_compensation_z)) ((ConfigOptionBool, gcode_comments)) ((ConfigOptionEnum, gcode_flavor)) ((ConfigOptionEnum, gcode_label_objects)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index d5ad93de01..d4b8736e49 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -2620,15 +2620,14 @@ PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &defau return config; } -void PrintObject::update_slicing_parameters() -{ - if (!m_slicing_params.valid) - m_slicing_params = SlicingParameters::create_from_config( - this->print()->config(), m_config, this->model_object()->max_z(), this->object_extruders()); +void PrintObject::update_slicing_parameters() { + if (!m_slicing_params.valid) { + m_slicing_params = SlicingParameters::create_from_config(this->print()->config(), m_config, this->model_object()->max_z(), + this->object_extruders(), this->print()->shrinkage_compensation()); + } } -SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig& full_config, const ModelObject& model_object, float object_max_z) -{ +SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full_config, const ModelObject &model_object, float object_max_z, const Vec3d &object_shrinkage_compensation) { PrintConfig print_config; PrintObjectConfig object_config; PrintRegionConfig default_region_config; @@ -2661,7 +2660,8 @@ SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig& full if (object_max_z <= 0.f) object_max_z = (float)model_object.raw_bounding_box().size().z(); - return SlicingParameters::create_from_config(print_config, object_config, object_max_z, object_extruders); + + return SlicingParameters::create_from_config(print_config, object_config, object_max_z, object_extruders, object_shrinkage_compensation); } // returns 0-based indices of extruders used to print the object (without brim, support and other helper extrusions) @@ -2682,7 +2682,6 @@ bool PrintObject::update_layer_height_profile(const ModelObject &model_object, c if (layer_height_profile.empty()) { // use the constructor because the assignement is crashing on ASAN OsX layer_height_profile = model_object.layer_height_profile.get(); -// layer_height_profile = model_object.layer_height_profile; // The layer height returned is sampled with high density for the UI layer height painting // and smoothing tool to work. updated = true; @@ -2693,8 +2692,9 @@ bool PrintObject::update_layer_height_profile(const ModelObject &model_object, c // Must not be of even length. ((layer_height_profile.size() & 1) != 0 || // Last entry must be at the top of the object. - std::abs(layer_height_profile[layer_height_profile.size() - 2] - slicing_parameters.object_print_z_max + slicing_parameters.object_print_z_min) > 1e-3)) + std::abs(layer_height_profile[layer_height_profile.size() - 2] - slicing_parameters.object_print_z_uncompensated_max + slicing_parameters.object_print_z_min) > 1e-3)) { layer_height_profile.clear(); + } if (layer_height_profile.empty()) { //layer_height_profile = layer_height_profile_adaptive(slicing_parameters, model_object.layer_config_ranges, model_object.volumes); @@ -3150,7 +3150,7 @@ static void project_triangles_to_slabs(SpanOfConstPtrs layers, const inde } void PrintObject::project_and_append_custom_facets( - bool seam, EnforcerBlockerType type, std::vector& out) const + bool seam, TriangleStateType type, std::vector& out) const { for (const ModelVolume* mv : this->model_object()->volumes) if (mv->is_model_part()) { diff --git a/src/libslic3r/Slicing.cpp b/src/libslic3r/Slicing.cpp index a2414d6998..091c46dc54 100644 --- a/src/libslic3r/Slicing.cpp +++ b/src/libslic3r/Slicing.cpp @@ -63,10 +63,11 @@ coordf_t Slicing::max_layer_height_from_nozzle(const DynamicPrintConfig &print_c } SlicingParameters SlicingParameters::create_from_config( - const PrintConfig &print_config, - const PrintObjectConfig &object_config, - coordf_t object_height, - const std::vector &object_extruders) + const PrintConfig &print_config, + const PrintObjectConfig &object_config, + coordf_t object_height, + const std::vector &object_extruders, + const Vec3d &object_shrinkage_compensation) { assert(! print_config.first_layer_height.percent); coordf_t first_layer_height = (print_config.first_layer_height.value <= 0) ? @@ -85,7 +86,9 @@ SlicingParameters SlicingParameters::create_from_config( params.first_print_layer_height = first_layer_height; params.first_object_layer_height = first_layer_height; params.object_print_z_min = 0.; - params.object_print_z_max = object_height; + params.object_print_z_max = object_height * object_shrinkage_compensation.z(); + params.object_print_z_uncompensated_max = object_height; + params.object_shrinkage_compensation_z = object_shrinkage_compensation.z(); params.base_raft_layers = object_config.raft_layers.value; params.soluble_interface = soluble_interface; @@ -152,6 +155,7 @@ SlicingParameters SlicingParameters::create_from_config( coordf_t print_z = params.raft_contact_top_z + params.gap_raft_object; params.object_print_z_min = print_z; params.object_print_z_max += print_z; + params.object_print_z_uncompensated_max += print_z; } params.valid = true; @@ -224,10 +228,10 @@ std::vector layer_height_profile_from_ranges( lh_append(hi, height); } - if (coordf_t z = last_z(); z < slicing_params.object_print_z_height()) { + if (coordf_t z = last_z(); z < slicing_params.object_print_z_uncompensated_height()) { // Insert a step of normal layer height up to the object top. lh_append(z, slicing_params.layer_height); - lh_append(slicing_params.object_print_z_height(), slicing_params.layer_height); + lh_append(slicing_params.object_print_z_uncompensated_height(), slicing_params.layer_height); } return layer_height_profile; @@ -418,12 +422,12 @@ void adjust_layer_height_profile( std::pair z_span_variable = std::pair( slicing_params.first_object_layer_height_fixed() ? slicing_params.first_object_layer_height : 0., - slicing_params.object_print_z_height()); + slicing_params.object_print_z_uncompensated_height()); if (z < z_span_variable.first || z > z_span_variable.second) return; assert(layer_height_profile.size() >= 2); - assert(std::abs(layer_height_profile[layer_height_profile.size() - 2] - slicing_params.object_print_z_height()) < EPSILON); + assert(std::abs(layer_height_profile[layer_height_profile.size() - 2] - slicing_params.object_print_z_uncompensated_height()) < EPSILON); // 1) Get the current layer thickness at z. coordf_t current_layer_height = slicing_params.layer_height; @@ -584,7 +588,7 @@ void adjust_layer_height_profile( assert(layer_height_profile.size() > 2); assert(layer_height_profile.size() % 2 == 0); assert(layer_height_profile[0] == 0.); - assert(std::abs(layer_height_profile[layer_height_profile.size() - 2] - slicing_params.object_print_z_height()) < EPSILON); + assert(std::abs(layer_height_profile[layer_height_profile.size() - 2] - slicing_params.object_print_z_uncompensated_height()) < EPSILON); #ifdef _DEBUG for (size_t i = 2; i < layer_height_profile.size(); i += 2) assert(layer_height_profile[i - 2] <= layer_height_profile[i]); @@ -613,6 +617,7 @@ std::vector generate_object_layers( out.push_back(print_z); } + const coordf_t shrinkage_compensation_z = slicing_params.object_shrinkage_compensation_z; size_t idx_layer_height_profile = 0; // loop until we have at least one layer and the max slice_z reaches the object height coordf_t slice_z = print_z + 0.5 * slicing_params.min_layer_height; @@ -621,17 +626,18 @@ std::vector generate_object_layers( if (idx_layer_height_profile < layer_height_profile.size()) { size_t next = idx_layer_height_profile + 2; for (;;) { - if (next >= layer_height_profile.size() || slice_z < layer_height_profile[next]) + if (next >= layer_height_profile.size() || slice_z < layer_height_profile[next] * shrinkage_compensation_z) break; idx_layer_height_profile = next; next += 2; } - coordf_t z1 = layer_height_profile[idx_layer_height_profile]; - coordf_t h1 = layer_height_profile[idx_layer_height_profile + 1]; + + const coordf_t z1 = layer_height_profile[idx_layer_height_profile] * shrinkage_compensation_z; + const coordf_t h1 = layer_height_profile[idx_layer_height_profile + 1]; height = h1; if (next < layer_height_profile.size()) { - coordf_t z2 = layer_height_profile[next]; - coordf_t h2 = layer_height_profile[next + 1]; + const coordf_t z2 = layer_height_profile[next] * shrinkage_compensation_z; + const coordf_t h2 = layer_height_profile[next + 1]; height = lerp(h1, h2, (slice_z - z1) / (z2 - z1)); assert(height >= slicing_params.min_layer_height - EPSILON && height <= slicing_params.max_layer_height + EPSILON); } diff --git a/src/libslic3r/Slicing.hpp b/src/libslic3r/Slicing.hpp index 7c2b93e21c..9e4b0499cc 100644 --- a/src/libslic3r/Slicing.hpp +++ b/src/libslic3r/Slicing.hpp @@ -33,10 +33,11 @@ struct SlicingParameters SlicingParameters() = default; static SlicingParameters create_from_config( - const PrintConfig &print_config, - const PrintObjectConfig &object_config, - coordf_t object_height, - const std::vector &object_extruders); + const PrintConfig &print_config, + const PrintObjectConfig &object_config, + coordf_t object_height, + const std::vector &object_extruders, + const Vec3d &object_shrinkage_compensation); // Has any raft layers? bool has_raft() const { return raft_layers() > 0; } @@ -46,8 +47,13 @@ struct SlicingParameters bool first_object_layer_height_fixed() const { return ! has_raft() || first_object_layer_bridging; } // Height of the object to be printed. This value does not contain the raft height. + // This value is scaled by shrinkage compensation in the Z-axis. coordf_t object_print_z_height() const { return object_print_z_max - object_print_z_min; } + // Height of the object to be printed. This value does not contain the raft height. + // This value isn't scaled by shrinkage compensation in the Z-axis. + coordf_t object_print_z_uncompensated_height() const { return object_print_z_uncompensated_max - object_print_z_min; } + bool valid { false }; // Number of raft layers. @@ -99,7 +105,13 @@ struct SlicingParameters coordf_t raft_contact_top_z { 0 }; // In case of a soluble interface, object_print_z_min == raft_contact_top_z, otherwise there is a gap between the raft and the 1st object layer. coordf_t object_print_z_min { 0 }; + // This value of maximum print Z is scaled by shrinkage compensation in the Z-axis. coordf_t object_print_z_max { 0 }; + + // This value of maximum print Z isn't scaled by shrinkage compensation. + coordf_t object_print_z_uncompensated_max { 0 }; + // Scaling factor for compensating shrinkage in Z-axis. + coordf_t object_shrinkage_compensation_z { 0 }; }; static_assert(IsTriviallyCopyable::value, "SlicingParameters class is not POD (and it should be - see constructor)."); diff --git a/src/libslic3r/Support/SupportMaterial.cpp b/src/libslic3r/Support/SupportMaterial.cpp index 937a4bf5df..25f204fb18 100644 --- a/src/libslic3r/Support/SupportMaterial.cpp +++ b/src/libslic3r/Support/SupportMaterial.cpp @@ -1106,8 +1106,8 @@ struct SupportAnnotations buildplate_covered(buildplate_covered) { // Append custom supports. - object.project_and_append_custom_facets(false, EnforcerBlockerType::ENFORCER, enforcers_layers); - object.project_and_append_custom_facets(false, EnforcerBlockerType::BLOCKER, blockers_layers); + object.project_and_append_custom_facets(false, TriangleStateType::ENFORCER, enforcers_layers); + object.project_and_append_custom_facets(false, TriangleStateType::BLOCKER, blockers_layers); } std::vector enforcers_layers; diff --git a/src/libslic3r/Support/TreeSupport.cpp b/src/libslic3r/Support/TreeSupport.cpp index 2f630df7cf..3161c85cb6 100644 --- a/src/libslic3r/Support/TreeSupport.cpp +++ b/src/libslic3r/Support/TreeSupport.cpp @@ -207,8 +207,8 @@ static std::vector>> group_me const int support_enforce_layers = config.support_material_enforce_layers.value; std::vector enforcers_layers{ print_object.slice_support_enforcers() }; std::vector blockers_layers{ print_object.slice_support_blockers() }; - print_object.project_and_append_custom_facets(false, EnforcerBlockerType::ENFORCER, enforcers_layers); - print_object.project_and_append_custom_facets(false, EnforcerBlockerType::BLOCKER, blockers_layers); + print_object.project_and_append_custom_facets(false, TriangleStateType::ENFORCER, enforcers_layers); + print_object.project_and_append_custom_facets(false, TriangleStateType::BLOCKER, blockers_layers); const int support_threshold = config.support_material_threshold.value; const bool support_threshold_auto = support_threshold == 0; // +1 makes the threshold inclusive diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 8324b5a4c9..c8c03fcfbe 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -234,7 +234,7 @@ int TriangleSelector::select_unsplit_triangle(const Vec3f &hit, int facet_idx) c return this->select_unsplit_triangle(hit, facet_idx, neighbors); } -void TriangleSelector::select_patch(int facet_start, std::unique_ptr &&cursor, EnforcerBlockerType new_state, const Transform3d& trafo_no_translate, bool triangle_splitting, float highlight_by_angle_deg) +void TriangleSelector::select_patch(int facet_start, std::unique_ptr &&cursor, TriangleStateType new_state, const Transform3d& trafo_no_translate, bool triangle_splitting, float highlight_by_angle_deg) { assert(facet_start < m_orig_size_indices); @@ -455,7 +455,7 @@ void TriangleSelector::bucket_fill_select_triangles(const Vec3f& hit, int facet_ return; assert(!m_triangles[start_facet_idx].is_split()); - EnforcerBlockerType start_facet_state = m_triangles[start_facet_idx].get_state(); + TriangleStateType start_facet_state = m_triangles[start_facet_idx].get_state(); this->seed_fill_unselect_all_triangles(); if (!propagate) { @@ -511,8 +511,7 @@ void TriangleSelector::bucket_fill_select_triangles(const Vec3f& hit, int facet_ // This is done by an actual recursive call. Returns false if the triangle is // outside the cursor. // Called by select_patch() and by itself. -bool TriangleSelector::select_triangle(int facet_idx, EnforcerBlockerType type, bool triangle_splitting) -{ +bool TriangleSelector::select_triangle(int facet_idx, TriangleStateType type, bool triangle_splitting) { assert(facet_idx < int(m_triangles.size())); if (! m_triangles[facet_idx].valid()) @@ -861,8 +860,7 @@ Vec3i TriangleSelector::child_neighbors_propagated(const Triangle &tr, const Vec return out; } -bool TriangleSelector::select_triangle_recursive(int facet_idx, const Vec3i &neighbors, EnforcerBlockerType type, bool triangle_splitting) -{ +bool TriangleSelector::select_triangle_recursive(int facet_idx, const Vec3i &neighbors, TriangleStateType type, bool triangle_splitting) { assert(facet_idx < int(m_triangles.size())); Triangle* tr = &m_triangles[facet_idx]; @@ -914,8 +912,7 @@ bool TriangleSelector::select_triangle_recursive(int facet_idx, const Vec3i &nei return true; } -void TriangleSelector::set_facet(int facet_idx, EnforcerBlockerType state) -{ +void TriangleSelector::set_facet(int facet_idx, TriangleStateType state) { assert(facet_idx < m_orig_size_indices); undivide_triangle(facet_idx); assert(! m_triangles[facet_idx].is_split()); @@ -934,7 +931,7 @@ void TriangleSelector::split_triangle(int facet_idx, const Vec3i &neighbors) Triangle* tr = &m_triangles[facet_idx]; assert(this->verify_triangle_neighbors(*tr, neighbors)); - EnforcerBlockerType old_type = tr->get_state(); + TriangleStateType old_type = tr->get_state(); // If we got here, we are about to actually split the triangle. const double limit_squared = m_edge_limit_sqr; @@ -1117,7 +1114,7 @@ void TriangleSelector::remove_useless_children(int facet_idx) // Return if a child is not leaf or two children differ in type. - EnforcerBlockerType first_child_type = EnforcerBlockerType::NONE; + TriangleStateType first_child_type = TriangleStateType::NONE; for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) { if (m_triangles[tr.children[child_idx]].is_split()) return; @@ -1217,8 +1214,7 @@ void TriangleSelector::set_edge_limit(float edge_limit) m_edge_limit_sqr = std::pow(edge_limit, 2.f); } -int TriangleSelector::push_triangle(int a, int b, int c, int source_triangle, const EnforcerBlockerType state) -{ +int TriangleSelector::push_triangle(int a, int b, int c, int source_triangle, const TriangleStateType state) { for (int i : {a, b, c}) { assert(i >= 0 && i < int(m_vertices.size())); ++m_vertices[i].ref_cnt; @@ -1251,8 +1247,7 @@ int TriangleSelector::push_triangle(int a, int b, int c, int source_triangle, co // Split a triangle based on Triangle::number_of_split_sides() and Triangle::special_side() // by allocating child triangles and midpoint vertices. // Midpoint vertices are possibly reused by traversing children of neighbor triangles. -void TriangleSelector::perform_split(int facet_idx, const Vec3i &neighbors, EnforcerBlockerType old_state) -{ +void TriangleSelector::perform_split(int facet_idx, const Vec3i &neighbors, TriangleStateType old_state) { // Reserve space for the new triangles upfront, so that the reference to this triangle will not change. { size_t num_triangles_new = m_triangles.size() + m_triangles[facet_idx].number_of_split_sides() + 1; @@ -1318,16 +1313,14 @@ void TriangleSelector::perform_split(int facet_idx, const Vec3i &neighbors, Enfo #endif // NDEBUG } -bool TriangleSelector::has_facets(EnforcerBlockerType state) const -{ +bool TriangleSelector::has_facets(TriangleStateType state) const { for (const Triangle& tr : m_triangles) if (tr.valid() && ! tr.is_split() && tr.get_state() == state) return true; return false; } -int TriangleSelector::num_facets(EnforcerBlockerType state) const -{ +int TriangleSelector::num_facets(TriangleStateType state) const { int cnt = 0; for (const Triangle& tr : m_triangles) if (tr.valid() && ! tr.is_split() && tr.get_state() == state) @@ -1335,8 +1328,7 @@ int TriangleSelector::num_facets(EnforcerBlockerType state) const return cnt; } -indexed_triangle_set TriangleSelector::get_facets(EnforcerBlockerType state) const -{ +indexed_triangle_set TriangleSelector::get_facets(TriangleStateType state) const { indexed_triangle_set out; std::vector vertex_map(m_vertices.size(), -1); for (const Triangle& tr : m_triangles) { @@ -1356,8 +1348,7 @@ indexed_triangle_set TriangleSelector::get_facets(EnforcerBlockerType state) con return out; } -indexed_triangle_set TriangleSelector::get_facets_strict(EnforcerBlockerType state) const -{ +indexed_triangle_set TriangleSelector::get_facets_strict(TriangleStateType state) const { indexed_triangle_set out; size_t num_vertices = 0; @@ -1385,7 +1376,7 @@ indexed_triangle_set TriangleSelector::get_facets_strict(EnforcerBlockerType sta void TriangleSelector::get_facets_strict_recursive( const Triangle &tr, const Vec3i &neighbors, - EnforcerBlockerType state, + TriangleStateType state, std::vector &out_triangles) const { if (tr.is_split()) { @@ -1526,12 +1517,11 @@ void TriangleSelector::get_seed_fill_contour_recursive(const int facet_idx, cons } } -std::pair>, std::vector> TriangleSelector::serialize() const -{ +TriangleSelector::TriangleSplittingData TriangleSelector::serialize() const { // Each original triangle of the mesh is assigned a number encoding its state // or how it is split. Each triangle is encoded by 4 bits (xxyy) or 8 bits (zzzzxxyy): - // leaf triangle: xx = EnforcerBlockerType (Only values 0, 1, and 2. Value 3 is used as an indicator for additional 4 bits.), yy = 0 - // leaf triangle: xx = 0b11, yy = 0b00, zzzz = EnforcerBlockerType (subtracted by 3) + // leaf triangle: xx = TriangleStateType (Only values 0, 1, and 2. Value 3 is used as an indicator for additional 4 bits.), yy = 0 + // leaf triangle: xx = 0b11, yy = 0b00, zzzz = TriangleStateType (subtracted by 3) // non-leaf: xx = special side, yy = number of split sides // These are bitwise appended and formed into one 64-bit integer. @@ -1543,7 +1533,7 @@ std::pair>, std::vector> TriangleSelector: // (std::function calls using a pointer, while this implementation calls directly). struct Serializer { const TriangleSelector* triangle_selector; - std::pair>, std::vector> data; + TriangleSplittingData data; void serialize(int facet_idx) { const Triangle& tr = triangle_selector->m_triangles[facet_idx]; @@ -1552,8 +1542,8 @@ std::pair>, std::vector> TriangleSelector: int split_sides = tr.number_of_split_sides(); assert(split_sides >= 0 && split_sides <= 3); - data.second.push_back(split_sides & 0b01); - data.second.push_back(split_sides & 0b10); + data.bitstream.push_back(split_sides & 0b01); + data.bitstream.push_back(split_sides & 0b10); if (split_sides) { // If this triangle is split, save which side is split (in case @@ -1561,8 +1551,8 @@ std::pair>, std::vector> TriangleSelector: // be ignored for 3-side split. assert(tr.is_split() && split_sides > 0); assert(tr.special_side() >= 0 && tr.special_side() <= 3); - data.second.push_back(tr.special_side() & 0b01); - data.second.push_back(tr.special_side() & 0b10); + data.bitstream.push_back(tr.special_side() & 0b01); + data.bitstream.push_back(tr.special_side() & 0b10); // Now save all children. // Serialized in reverse order for compatibility with PrusaSlicer 2.3.1. for (int child_idx = split_sides; child_idx >= 0; -- child_idx) @@ -1570,48 +1560,50 @@ std::pair>, std::vector> TriangleSelector: } else { // In case this is leaf, we better save information about its state. int n = int(tr.get_state()); + if (n < static_cast(TriangleStateType::Count)) + data.used_states[n] = true; + if (n >= 3) { assert(n <= 16); if (n <= 16) { // Store "11" plus 4 bits of (n-3). - data.second.insert(data.second.end(), { true, true }); + data.bitstream.insert(data.bitstream.end(), { true, true }); n -= 3; for (size_t bit_idx = 0; bit_idx < 4; ++bit_idx) - data.second.push_back(n & (uint64_t(0b0001) << bit_idx)); + data.bitstream.push_back(n & (uint64_t(0b0001) << bit_idx)); } } else { // Simple case, compatible with PrusaSlicer 2.3.1 and older for storing paint on supports and seams. // Store 2 bits of n. - data.second.push_back(n & 0b01); - data.second.push_back(n & 0b10); + data.bitstream.push_back(n & 0b01); + data.bitstream.push_back(n & 0b10); } } } } out { this }; - out.data.first.reserve(m_orig_size_indices); + out.data.triangles_to_split.reserve(m_orig_size_indices); for (int i=0; i>, std::vector> &data, bool needs_reset) -{ +void TriangleSelector::deserialize(const TriangleSplittingData &data, bool needs_reset) { if (needs_reset) reset(); // dump any current state // Reserve number of triangles as if each triangle was saved with 4 bits. // With MMU painting this estimate may be somehow low, but better than nothing. - m_triangles.reserve(std::max(m_mesh.its.indices.size(), data.second.size() / 4)); + m_triangles.reserve(std::max(m_mesh.its.indices.size(), data.bitstream.size() / 4)); // Number of triangles is twice the number of vertices on a large manifold mesh of genus zero. // Here the triangles count account for both the nodes and leaves, thus the following line may overestimate. m_vertices.reserve(std::max(m_mesh.its.vertices.size(), m_triangles.size() / 2)); @@ -1627,13 +1619,13 @@ void TriangleSelector::deserialize(const std::pair parents; - for (auto [triangle_id, ibit] : data.first) { + for (auto [triangle_id, ibit] : data.triangles_to_split) { assert(triangle_id < int(m_triangles.size())); - assert(ibit < int(data.second.size())); + assert(ibit < int(data.bitstream.size())); auto next_nibble = [&data, &ibit = ibit]() { int n = 0; for (int i = 0; i < 4; ++ i) - n |= data.second[ibit ++] << i; + n |= data.bitstream[ibit ++] << i; return n; }; @@ -1645,7 +1637,7 @@ void TriangleSelector::deserialize(const std::pair> 2); + auto state = is_split ? TriangleStateType::NONE : TriangleStateType((code & 0b1100) == 0b1100 ? next_nibble() + 3 : code >> 2); // Only valid if is_split. int special_side = code >> 2; @@ -1657,7 +1649,7 @@ void TriangleSelector::deserialize(const std::pairchild_neighbors(tr, last.neighbors, child_idx); int this_idx = tr.children[child_idx]; m_triangles[this_idx].set_division(num_of_split_sides, special_side); - perform_split(this_idx, neighbors, EnforcerBlockerType::NONE); + perform_split(this_idx, neighbors, TriangleStateType::NONE); parents.push_back({this_idx, neighbors, 0, num_of_children}); } else { // this triangle belongs to last split one @@ -1705,21 +1697,53 @@ void TriangleSelector::deserialize(const std::pairbitstream.size()); + assert(!this->bitstream.empty() && this->bitstream.size() != bitstream_start_idx); + assert((this->bitstream.size() - bitstream_start_idx) % 4 == 0); + + if (this->bitstream.empty() || this->bitstream.size() == bitstream_start_idx) + return; + + size_t nibble_idx = bitstream_start_idx; + + auto read_next_nibble = [&data_bitstream = std::as_const(this->bitstream), &nibble_idx]() -> uint8_t { + assert(nibble_idx + 3 < data_bitstream.size()); + uint8_t code = 0; + for (size_t bit_idx = 0; bit_idx < 4; ++bit_idx) + code |= data_bitstream[nibble_idx++] << bit_idx; + return code; + }; + + while (nibble_idx < this->bitstream.size()) { + const uint8_t code = read_next_nibble(); + + if (const bool is_split = (code & 0b11) != 0; is_split) + continue; + + const uint8_t facet_state = (code & 0b1100) == 0b1100 ? read_next_nibble() + 3 : code >> 2; + assert(facet_state < this->used_states.size()); + if (facet_state >= this->used_states.size()) + continue; + + this->used_states[facet_state] = true; + } +} + // Lightweight variant of deserialization, which only tests whether a face of test_state exists. -bool TriangleSelector::has_facets(const std::pair>, std::vector> &data, const EnforcerBlockerType test_state) -{ +bool TriangleSelector::has_facets(const TriangleSplittingData &data, const TriangleStateType test_state) { // Depth-first queue of a number of unvisited children. // Kept outside of the loop to avoid re-allocating inside the loop. std::vector parents_children; parents_children.reserve(64); - for (const std::pair &triangle_id_and_ibit : data.first) { - int ibit = triangle_id_and_ibit.second; - assert(ibit < int(data.second.size())); + for (const TriangleBitStreamMapping &triangle_id_and_ibit : data.triangles_to_split) { + int ibit = triangle_id_and_ibit.bitstream_start_idx; + assert(ibit < int(data.bitstream.size())); auto next_nibble = [&data, &ibit = ibit]() { int n = 0; for (int i = 0; i < 4; ++ i) - n |= data.second[ibit ++] << i; + n |= data.bitstream[ibit ++] << i; return n; }; // < 0 -> negative of a number of children @@ -1764,8 +1788,7 @@ void TriangleSelector::seed_fill_unselect_all_triangles() triangle.unselect_by_seed_fill(); } -void TriangleSelector::seed_fill_apply_on_triangles(EnforcerBlockerType new_state) -{ +void TriangleSelector::seed_fill_apply_on_triangles(TriangleStateType new_state) { for (Triangle &triangle : m_triangles) if (!triangle.is_split() && triangle.is_selected_by_seed_fill()) triangle.set_state(new_state); diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index 0d6a113943..0e88fe3a7c 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -14,8 +14,29 @@ namespace Slic3r { -enum class EnforcerBlockerType : int8_t; - +enum class TriangleStateType : int8_t { + // Maximum is 3. The value is serialized in TriangleSelector into 2 bits. + NONE = 0, + ENFORCER = 1, + BLOCKER = 2, + // Maximum is 15. The value is serialized in TriangleSelector into 6 bits using a 2 bit prefix code. + Extruder1 = ENFORCER, + Extruder2 = BLOCKER, + Extruder3, + Extruder4, + Extruder5, + Extruder6, + Extruder7, + Extruder8, + Extruder9, + Extruder10, + Extruder11, + Extruder12, + Extruder13, + Extruder14, + Extruder15, + Count +}; // Following class holds information about selected triangles. It also has power // to recursively subdivide the triangles and make the selection finer. @@ -184,6 +205,56 @@ public: } }; + struct TriangleBitStreamMapping + { + // Index of the triangle to which we assign the bitstream containing splitting information. + int triangle_idx = -1; + // Index of the first bit of the bitstream assigned to this triangle. + int bitstream_start_idx = -1; + + TriangleBitStreamMapping() = default; + explicit TriangleBitStreamMapping(int triangleIdx, int bitstreamStartIdx) : triangle_idx(triangleIdx), bitstream_start_idx(bitstreamStartIdx) {} + + friend bool operator==(const TriangleBitStreamMapping &lhs, const TriangleBitStreamMapping &rhs) { return lhs.triangle_idx == rhs.triangle_idx && lhs.bitstream_start_idx == rhs.bitstream_start_idx; } + friend bool operator!=(const TriangleBitStreamMapping &lhs, const TriangleBitStreamMapping &rhs) { return !(lhs == rhs); } + + private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(triangle_idx, bitstream_start_idx); } + }; + + struct TriangleSplittingData { + // Vector of triangles and its indexes to the bitstream. + std::vector triangles_to_split; + // Bit stream containing splitting information. + std::vector bitstream; + // Array indicating which triangle state types are used (encoded inside bitstream). + std::vector used_states { std::vector(static_cast(TriangleStateType::Count), false) }; + + TriangleSplittingData() = default; + + friend bool operator==(const TriangleSplittingData &lhs, const TriangleSplittingData &rhs) { + return lhs.triangles_to_split == rhs.triangles_to_split + && lhs.bitstream == rhs.bitstream + && lhs.used_states == rhs.used_states; + } + + friend bool operator!=(const TriangleSplittingData &lhs, const TriangleSplittingData &rhs) { return !(lhs == rhs); } + + // Reset all used states before they are recomputed based on the bitstream. + void reset_used_states() { + used_states.resize(static_cast(TriangleStateType::Count), false); + std::fill(used_states.begin(), used_states.end(), false); + } + + // Update used states based on the bitstream. It just iterated over the bitstream from the bitstream_start_idx till the end. + void update_used_states(size_t bitstream_start_idx); + + private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(triangles_to_split, bitstream, used_states); } + }; + std::pair, std::vector> precompute_all_neighbors() const; void precompute_all_neighbors_recursive(int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector &neighbors_out, std::vector &neighbors_normal_out) const; @@ -202,7 +273,7 @@ public: // Select all triangles fully inside the circle, subdivide where needed. void select_patch(int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to std::unique_ptr &&cursor, // Cursor containing information about the point where to start, camera position (mesh coords), matrix to get from mesh to world, and its shape and type. - EnforcerBlockerType new_state, // enforcer or blocker? + TriangleStateType new_state, // enforcer or blocker? const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation bool triangle_splitting, // If triangles will be split base on the cursor or not float highlight_by_angle_deg = 0.f); // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees. @@ -221,18 +292,18 @@ public: bool propagate, // if bucket fill is propagated to neighbor faces or if it fills the only facet of the modified mesh that the hit point belongs to. bool force_reselection = false); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle - bool has_facets(EnforcerBlockerType state) const; - static bool has_facets(const std::pair>, std::vector> &data, EnforcerBlockerType test_state); - int num_facets(EnforcerBlockerType state) const; + bool has_facets(TriangleStateType state) const; + static bool has_facets(const TriangleSplittingData &data, TriangleStateType test_state); + int num_facets(TriangleStateType state) const; // Get facets at a given state. Don't triangulate T-joints. - indexed_triangle_set get_facets(EnforcerBlockerType state) const; + indexed_triangle_set get_facets(TriangleStateType state) const; // Get facets at a given state. Triangulate T-joints. - indexed_triangle_set get_facets_strict(EnforcerBlockerType state) const; + indexed_triangle_set get_facets_strict(TriangleStateType state) const; // Get edges around the selected area by seed fill. std::vector get_seed_fill_contour() const; // Set facet of the mesh to a given state. Only works for original triangles. - void set_facet(int facet_idx, EnforcerBlockerType state); + void set_facet(int facet_idx, TriangleStateType state); // Clear everything and make the tree empty. void reset(); @@ -242,17 +313,20 @@ public: // Store the division trees in compact form (a long stream of bits for each triangle of the original mesh). // First vector contains pairs of (triangle index, first bit in the second vector). - std::pair>, std::vector> serialize() const; + TriangleSplittingData serialize() const; // Load serialized data. Assumes that correct mesh is loaded. - void deserialize(const std::pair>, std::vector> &data, bool needs_reset = true); + void deserialize(const TriangleSplittingData &data, bool needs_reset = true); + + // Extract all used facet states from the given TriangleSplittingData. + static std::vector extract_used_facet_states(const TriangleSplittingData &data); // For all triangles, remove the flag indicating that the triangle was selected by seed fill. void seed_fill_unselect_all_triangles(); - // For all triangles selected by seed fill, set new EnforcerBlockerType and remove flag indicating that triangle was selected by seed fill. + // For all triangles selected by seed fill, set new TriangleStateType and remove flag indicating that triangle was selected by seed fill. // The operation may merge split triangles if they are being assigned the same color. - void seed_fill_apply_on_triangles(EnforcerBlockerType new_state); + void seed_fill_apply_on_triangles(TriangleStateType new_state); protected: // Triangle and info about how it's split. @@ -260,7 +334,7 @@ protected: public: // Use TriangleSelector::push_triangle to create a new triangle. // It increments/decrements reference counter on vertices. - Triangle(int a, int b, int c, int source_triangle, const EnforcerBlockerType init_state) + Triangle(int a, int b, int c, int source_triangle, const TriangleStateType init_state) : verts_idxs{a, b, c}, source_triangle{source_triangle}, state{init_state} @@ -282,8 +356,8 @@ protected: void set_division(int sides_to_split, int special_side_idx); // Get/set current state. - void set_state(EnforcerBlockerType type) { assert(! is_split()); state = type; } - EnforcerBlockerType get_state() const { assert(! is_split()); return state; } + void set_state(TriangleStateType type) { assert(! is_split()); state = type; } + TriangleStateType get_state() const { assert(! is_split()); return state; } // Set if the triangle has been selected or unselected by seed fill. void select_by_seed_fill() { assert(! is_split()); m_selected_by_seed_fill = true; } @@ -307,7 +381,7 @@ protected: // or index of a vertex shared by the two split edges (for number_of_splits == 2). // For number_of_splits == 3, special_side_idx is always zero. char special_side_idx { 0 }; - EnforcerBlockerType state; + TriangleStateType state; bool m_selected_by_seed_fill : 1; // Is this triangle valid or marked to be removed? bool m_valid : 1; @@ -345,14 +419,14 @@ protected: // Private functions: private: - bool select_triangle(int facet_idx, EnforcerBlockerType type, bool triangle_splitting); - bool select_triangle_recursive(int facet_idx, const Vec3i &neighbors, EnforcerBlockerType type, bool triangle_splitting); + bool select_triangle(int facet_idx, TriangleStateType type, bool triangle_splitting); + bool select_triangle_recursive(int facet_idx, const Vec3i &neighbors, TriangleStateType type, bool triangle_splitting); void undivide_triangle(int facet_idx); void split_triangle(int facet_idx, const Vec3i &neighbors); void remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant. bool is_facet_clipped(int facet_idx, const ClippingPlane &clp) const; - int push_triangle(int a, int b, int c, int source_triangle, EnforcerBlockerType state = EnforcerBlockerType{0}); - void perform_split(int facet_idx, const Vec3i &neighbors, EnforcerBlockerType old_state); + int push_triangle(int a, int b, int c, int source_triangle, TriangleStateType state = TriangleStateType::NONE); + void perform_split(int facet_idx, const Vec3i &neighbors, TriangleStateType old_state); Vec3i child_neighbors(const Triangle &tr, const Vec3i &neighbors, int child_idx) const; Vec3i child_neighbors_propagated(const Triangle &tr, const Vec3i &neighbors_propagated, int child_idx, const Vec3i &child_neighbors) const; // Return child of itriangle at a CCW oriented side (vertexi, vertexj), either first or 2nd part. @@ -381,7 +455,7 @@ private: void get_facets_strict_recursive( const Triangle &tr, const Vec3i &neighbors, - EnforcerBlockerType state, + TriangleStateType state, std::vector &out_triangles) const; void get_facets_split_by_tjoints(const Vec3i &vertices, const Vec3i &neighbors, std::vector &out_triangles) const; diff --git a/src/libslic3r/TriangleSelectorWrapper.cpp b/src/libslic3r/TriangleSelectorWrapper.cpp index 226d1f6b47..517170ed60 100644 --- a/src/libslic3r/TriangleSelectorWrapper.cpp +++ b/src/libslic3r/TriangleSelectorWrapper.cpp @@ -29,7 +29,7 @@ void TriangleSelectorWrapper::enforce_spot(const Vec3f &point, const Vec3f &orig if ((point - pos).norm() < radius && face_normal.dot(dir) < 0) { std::unique_ptr cursor = std::make_unique( pos, origin, radius, this->mesh_transform, TriangleSelector::ClippingPlane { }); - selector.select_patch(hit.id, std::move(cursor), EnforcerBlockerType::ENFORCER, trafo_no_translate, + selector.select_patch(hit.id, std::move(cursor), TriangleStateType::ENFORCER, trafo_no_translate, true, eps_angle); break; } @@ -42,7 +42,7 @@ void TriangleSelectorWrapper::enforce_spot(const Vec3f &point, const Vec3f &orig if (dist < radius) { std::unique_ptr cursor = std::make_unique( point, origin, radius, this->mesh_transform, TriangleSelector::ClippingPlane { }); - selector.select_patch(hit_idx_out, std::move(cursor), EnforcerBlockerType::ENFORCER, + selector.select_patch(hit_idx_out, std::move(cursor), TriangleStateType::ENFORCER, trafo_no_translate, true, eps_angle); } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 5e19f3fe27..65090b6676 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -628,11 +628,10 @@ void GLCanvas3D::LayersEditing::accept_changes(GLCanvas3D& canvas) m_layer_height_profile_modified = false; } -void GLCanvas3D::LayersEditing::update_slicing_parameters() -{ - if (m_slicing_parameters == nullptr) { - m_slicing_parameters = new SlicingParameters(); - *m_slicing_parameters = PrintObject::slicing_parameters(*m_config, *m_model_object, m_object_max_z); +void GLCanvas3D::LayersEditing::update_slicing_parameters() { + if (m_slicing_parameters == nullptr) { + m_slicing_parameters = new SlicingParameters(); + *m_slicing_parameters = PrintObject::slicing_parameters(*m_config, *m_model_object, m_object_max_z, m_shrinkage_compensation); } } @@ -1605,9 +1604,11 @@ void GLCanvas3D::set_config(const DynamicPrintConfig* config) m_config = config; m_layers_editing.set_config(config); - if (config) { - PrinterTechnology ptech = current_printer_technology(); + const PrinterTechnology ptech = current_printer_technology(); + if (const Print *print = fff_print(); ptech == ptFFF && print != nullptr) + m_layers_editing.set_shrinkage_compensation(fff_print()->shrinkage_compensation()); + if (config) { auto slot = ArrangeSettingsDb_AppCfg::slotFFF; if (ptech == ptSLA) { @@ -3200,9 +3201,9 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt) } } - // If Undo/Redo list is opened, + // If Undo/Redo list is opened, // update them according to the event - if (m_undoredo_toolbar.is_item_pressed("undo") || + if (m_undoredo_toolbar.is_item_pressed("undo") || m_undoredo_toolbar.is_item_pressed("redo")) { m_mouse_wheel = int((double)evt.GetWheelRotation() / (double)evt.GetWheelDelta()); return; diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 09ff631ed5..3975b02b08 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -219,6 +219,8 @@ class GLCanvas3D SlicingParameters *m_slicing_parameters{ nullptr }; std::vector m_layer_height_profile; bool m_layer_height_profile_modified{ false }; + // Shrinkage compensation to apply when we need to use object_max_z with Z compensation. + Vec3d m_shrinkage_compensation{ Vec3d::Ones() }; mutable float m_adaptive_quality{ 0.5f }; mutable HeightProfileSmoothingParams m_smooth_params; @@ -299,6 +301,8 @@ class GLCanvas3D std::pair> get_layers_height_data(); + void set_shrinkage_compensation(const Vec3d &shrinkage_compensation) { m_shrinkage_compensation = shrinkage_compensation; }; + private: bool is_initialized() const; void generate_layer_height_texture(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 4bbdf6d5d9..4f3b472e21 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -374,7 +374,7 @@ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block) const indexed_triangle_set &its = mv->mesh().its; for (const stl_triangle_vertex_indices &face : its.indices) { if (its_face_normal(its, face).dot(down) > dot_limit) { - m_triangle_selectors[mesh_id]->set_facet(idx, block ? EnforcerBlockerType::BLOCKER : EnforcerBlockerType::ENFORCER); + m_triangle_selectors[mesh_id]->set_facet(idx, block ? TriangleStateType::BLOCKER : TriangleStateType::ENFORCER); m_triangle_selectors.back()->request_update_render_data(); } ++ idx; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp index 0f343e586c..ad4931058d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp @@ -110,8 +110,8 @@ protected: ColorRGBA get_cursor_sphere_left_button_color() const override; ColorRGBA get_cursor_sphere_right_button_color() const override; - EnforcerBlockerType get_left_button_state_type() const override { return EnforcerBlockerType(m_first_selected_extruder_idx + 1); } - EnforcerBlockerType get_right_button_state_type() const override { return EnforcerBlockerType(m_second_selected_extruder_idx + 1); } + TriangleStateType get_left_button_state_type() const override { return TriangleStateType(m_first_selected_extruder_idx + 1); } + TriangleStateType get_right_button_state_type() const override { return TriangleStateType(m_second_selected_extruder_idx + 1); } void on_render_input_window(float x, float y, float bottom_limit) override; std::string on_get_name() const override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 29f0b134b6..8d0fad0883 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -503,7 +503,7 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous if (m_triangle_selectors.empty()) return false; - EnforcerBlockerType new_state = EnforcerBlockerType::NONE; + TriangleStateType new_state = TriangleStateType::NONE; if (! shift_down) { if (action == SLAGizmoEventType::Dragging) new_state = m_button_down == Button::Left ? this->get_left_button_state_type() : this->get_right_button_state_type(); @@ -937,16 +937,16 @@ void TriangleSelectorGUI::update_render_data() static const float offset = 0.001f; for (const Triangle &tr : m_triangles) { - if (!tr.valid() || tr.is_split() || (tr.get_state() == EnforcerBlockerType::NONE && !tr.is_selected_by_seed_fill())) + if (!tr.valid() || tr.is_split() || (tr.get_state() == TriangleStateType::NONE && !tr.is_selected_by_seed_fill())) continue; int tr_state = int(tr.get_state()); - GLModel::Geometry &iva = tr.is_selected_by_seed_fill() ? iva_seed_fills_data[tr_state] : - tr.get_state() == EnforcerBlockerType::ENFORCER ? iva_enforcers_data : - iva_blockers_data; - int &cnt = tr.is_selected_by_seed_fill() ? seed_fill_cnt[tr_state] : - tr.get_state() == EnforcerBlockerType::ENFORCER ? enf_cnt : - blc_cnt; + GLModel::Geometry &iva = tr.is_selected_by_seed_fill() ? iva_seed_fills_data[tr_state] : + tr.get_state() == TriangleStateType::ENFORCER ? iva_enforcers_data : + iva_blockers_data; + int &cnt = tr.is_selected_by_seed_fill() ? seed_fill_cnt[tr_state] : + tr.get_state() == TriangleStateType::ENFORCER ? enf_cnt : + blc_cnt; const Vec3f &v0 = m_vertices[tr.verts_idxs[0]].v; const Vec3f &v1 = m_vertices[tr.verts_idxs[1]].v; const Vec3f &v2 = m_vertices[tr.verts_idxs[2]].v; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index db974205fb..bd185fce0e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -120,8 +120,8 @@ protected: virtual ColorRGBA get_cursor_sphere_left_button_color() const { return { 0.0f, 0.0f, 1.0f, 0.25f }; } virtual ColorRGBA get_cursor_sphere_right_button_color() const { return { 1.0f, 0.0f, 0.0f, 0.25f }; } - virtual EnforcerBlockerType get_left_button_state_type() const { return EnforcerBlockerType::ENFORCER; } - virtual EnforcerBlockerType get_right_button_state_type() const { return EnforcerBlockerType::BLOCKER; } + virtual TriangleStateType get_left_button_state_type() const { return TriangleStateType::ENFORCER; } + virtual TriangleStateType get_right_button_state_type() const { return TriangleStateType::BLOCKER; } float m_cursor_radius = 2.f; static constexpr float CursorRadiusMin = 0.4f; // cannot be zero diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 05dd363dbc..f3a2aa08fe 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -136,6 +136,10 @@ enum class NotificationType SelectFilamentFromConnect, // Debug notification for connect communication PrusaConnectPrinters, + // Notification that bed temperatures for the used filaments differ significantly. + BedTemperaturesDiffer, + // Notification that shrinkage compensations for the used filaments differ. + ShrinkageCompensationsDiffer, }; class NotificationManager diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 62e0c5f6b6..b48edca87c 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1916,9 +1916,14 @@ void Plater::priv::process_validation_warning(const std::vector& wa if (warnings.empty()) notification_manager->close_notification_of_type(NotificationType::ValidateWarning); - for (std::string text : warnings) { - std::string hypertext = ""; - std::function action_fn = [](wxEvtHandler*){ return false; }; + // Always close warnings BedTemperaturesDiffer and ShrinkageCompensationsDiffer before next processing. + notification_manager->close_notification_of_type(NotificationType::BedTemperaturesDiffer); + notification_manager->close_notification_of_type(NotificationType::ShrinkageCompensationsDiffer); + + for (std::string text : warnings) { + std::string hypertext = ""; + NotificationType notification_type = NotificationType::ValidateWarning; + std::function action_fn = [](wxEvtHandler*){ return false; }; if (text == "_SUPPORTS_OFF") { text = _u8L("An object has custom support enforcers which will not be used " @@ -1934,12 +1939,17 @@ void Plater::priv::process_validation_warning(const std::vector& wa print_tab->on_value_change("support_material_auto", config.opt_bool("support_material_auto")); return true; }; + } else if (text == "_BED_TEMPS_DIFFER") { + text = _u8L("Bed temperatures for the used filaments differ significantly."); + notification_type = NotificationType::BedTemperaturesDiffer; + } else if (text == "_FILAMENT_SHRINKAGE_DIFFER") { + text = _u8L("Filament shrinkage will not be used because filament shrinkage " + "for the used filaments differs significantly."); + notification_type = NotificationType::ShrinkageCompensationsDiffer; } - if (text == "_BED_TEMPS_DIFFER") - text = _u8L("Bed temperatures for the used filaments differ significantly."); notification_manager->push_notification( - NotificationType::ValidateWarning, + notification_type, NotificationManager::NotificationLevel::WarningNotificationLevel, _u8L("WARNING:") + "\n" + text, hypertext, action_fn ); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index fdf0d3a339..017de90fcd 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2237,6 +2237,10 @@ void TabFilament::build() }; optgroup->append_line(line); + optgroup = page->new_optgroup(L("Shrinkage compensation")); + optgroup->append_single_option_line("filament_shrinkage_compensation_xy"); + optgroup->append_single_option_line("filament_shrinkage_compensation_z"); + optgroup = page->new_optgroup(L("Wipe tower parameters")); optgroup->append_single_option_line("filament_minimal_purge_on_wipe_tower");