diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 0cf67597a3..45a22a2708 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -169,6 +169,7 @@ add_library(libslic3r STATIC PrintConfig.cpp PrintConfig.hpp PrintObject.cpp + PrintObjectSlice.cpp PrintRegion.cpp PNGReadWrite.hpp PNGReadWrite.cpp diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index bfad61a90d..c6f2b5b1bf 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1667,6 +1667,8 @@ ModelVolumeType ModelVolume::type_from_string(const std::string &s) // New type (supporting the support enforcers & blockers) if (s == "ModelPart") return ModelVolumeType::MODEL_PART; + if (s == "NegativeVolume") + return ModelVolumeType::NEGATIVE_VOLUME; if (s == "ParameterModifier") return ModelVolumeType::PARAMETER_MODIFIER; if (s == "SupportEnforcer") @@ -1682,6 +1684,7 @@ std::string ModelVolume::type_to_string(const ModelVolumeType t) { switch (t) { case ModelVolumeType::MODEL_PART: return "ModelPart"; + case ModelVolumeType::NEGATIVE_VOLUME: return "NegativeVolume"; case ModelVolumeType::PARAMETER_MODIFIER: return "ParameterModifier"; case ModelVolumeType::SUPPORT_ENFORCER: return "SupportEnforcer"; case ModelVolumeType::SUPPORT_BLOCKER: return "SupportBlocker"; diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index a65332272c..7b782e87d4 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -482,9 +482,10 @@ private: enum class ModelVolumeType : int { INVALID = -1, MODEL_PART = 0, + NEGATIVE_VOLUME, PARAMETER_MODIFIER, - SUPPORT_ENFORCER, SUPPORT_BLOCKER, + SUPPORT_ENFORCER, }; enum class EnforcerBlockerType : int8_t { @@ -591,6 +592,7 @@ public: ModelVolumeType type() const { return m_type; } void set_type(const ModelVolumeType t) { m_type = t; } bool is_model_part() const { return m_type == ModelVolumeType::MODEL_PART; } + bool is_negative_volume() const { return m_type == ModelVolumeType::NEGATIVE_VOLUME; } bool is_modifier() const { return m_type == ModelVolumeType::PARAMETER_MODIFIER; } bool is_support_enforcer() const { return m_type == ModelVolumeType::SUPPORT_ENFORCER; } bool is_support_blocker() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER; } @@ -813,6 +815,16 @@ private: } }; +inline void model_volumes_sort_by_id(ModelVolumePtrs &model_volumes) +{ + std::sort(model_volumes.begin(), model_volumes.end(), [](const ModelVolume *l, const ModelVolume *r) { return l->id() < r->id(); }); +} + +inline const ModelVolume* model_volume_find_by_id(const ModelVolumePtrs &model_volumes, const ObjectID id) +{ + auto it = lower_bound_by_predicate(model_volumes.begin(), model_volumes.end(), [id](const ModelVolume *mv) { return mv->id() < id; }); + return it != model_volume.end() && (*it)->id() == id ? *it : nullptr; +} enum ModelInstanceEPrintVolumeState : unsigned char { diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index b4241c91ec..cd3f84df5c 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -74,6 +74,7 @@ public: size_t config_hash() const throw() { return m_config_hash; } // Identifier of this PrintRegion in the list of Print::m_print_regions. int print_region_id() const throw() { return m_print_region_id; } + int print_object_region_id() const throw() { return m_print_object_region_id; } // 1-based extruder identifier for this region and role. unsigned int extruder(FlowRole role) const; Flow flow(const PrintObject &object, FlowRole role, double layer_height, bool first_layer = false) const; @@ -96,7 +97,9 @@ private: friend Print; PrintRegionConfig m_config; size_t m_config_hash; - int m_print_region_id = -1; + int m_print_region_id { -1 }; + int m_print_object_region_id { -1 }; + int m_ref_cnt { 0 }; }; inline bool operator==(const PrintRegion &lhs, const PrintRegion &rhs) { return lhs.config_hash() == rhs.config_hash() && lhs.config() == rhs.config(); } @@ -152,32 +155,74 @@ struct PrintInstance typedef std::vector PrintInstances; -// Region and its volumes (printing volumes or modifier volumes) -struct PrintRegionVolumes +class PrintObjectRegions { - // Single volume + Z range assigned to a region. - struct VolumeWithZRange { - // Z range to slice this ModelVolume over. - t_layer_height_range layer_height_range; - // Index of a ModelVolume inside its parent ModelObject. - int volume_idx; +public: + // Bounding box of a ModelVolume transformed into the working space of a PrintObject, possibly + // clipped by a layer range modifier. + struct VolumeExtents { + ObjectID volume_id; + BoundingBoxf bbox; }; - // Overriding one region with some other extruder, producing another region. - // The region is owned by PrintObject::m_all_regions. - struct ExtruderOverride { - unsigned int extruder; -// const PrintRegion *region; + struct VolumeRegion + { + // ID of the associated ModelVolume. + const ModelVolume *model_volume { nullptr }; + // Index of a parent VolumeRegion. + int parent { -1 }; + // Pointer to PrintObjectRegions::all_regions, null for a negative volume. + PrintRegion *region { nullptr }; + // Pointer to VolumeExtents::bbox. + const BoundingBoxf *bbox { nullptr }; + // To speed up merging of same regions. + const VolumeRegion *prev_same_region { nullptr }; }; - // The region is owned by PrintObject::m_all_regions. -// const PrintRegion *region; - // Possible overrides of the default region extruder. - std::vector overrides; - // List of ModelVolume indices and layer ranges of thereof. - std::vector volumes; - // Is this region printing in any layer? -// bool printing { false }; + struct PaintedRegion + { + // 1-based extruder identifier. + unsigned int extruder_id; + // Index of a parent VolumeRegion. + int parent { -1 }; + // Pointer to PrintObjectRegions::all_regions. + PrintRegion *region { nullptr }; + } + + // One slice over the PrintObject (possibly the whole PrintObject) and a list of ModelVolumes and their bounding boxes + // possibly clipped by the layer_height_range. + struct LayerRangeRegions + { + t_layer_height_range layer_height_range; + // Config of the layer range, null if there is just a single range with no config override. + // Config is owned by the associated ModelObject. + const DynamicPrintConfig* config { nullptr }; + // Volumes sorted by ModelVolume::id(). + std::vector volumes; + + bool has_volume(const ObjectID id) { + auto it = lower_bound_by_predicate(this->volumes.begin(), this->volumes.end(), [id](const VolumeExtents &v){ return v.volume_id == id; }); + return it != this->volumes.end() && it->volume_id == id; + } + + // Sorted in the order of their source ModelVolumes, thus reflecting the order of region clipping, modifier overrides etc. + std::vector volume_regions; + std::vector painted_regions; + }; + + std::vector> all_regions; + std::vector layer_ranges; + // Transformation of this ModelObject into one of the associated PrintObjects (all PrintObjects derived from a single modelObject differ by a Z rotation only). + // This transformation is used to calculate VolumeExtents. + Matrix3x3f trafo_bboxes; + + size_t ref_cnt_inc() { ++ this->ref_cnt; } + size_t ref_cnt_dec() { if (-- this->ref_cnt == 0) delete *this; } + +private: + // Number of PrintObjects generated from the same ModelObject and sharing the regions. + // ref_cnt could only be modified by the main thread, thus it does not need to be atomic. + size_t m_ref_cnt; }; class PrintObject : public PrintObjectBaseWithState @@ -270,7 +315,7 @@ public: void slice(); // Helpers to slice support enforcer / blocker meshes by the support generator. - std::vector slice_support_volumes(const ModelVolumeType &model_volume_type) const; + std::vector slice_support_volumes(const ModelVolumeType model_volume_type) const; std::vector slice_support_blockers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_BLOCKER); } std::vector slice_support_enforcers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_ENFORCER); } @@ -282,8 +327,8 @@ private: friend class Print; PrintObject(Print* print, ModelObject* model_object, const Transform3d& trafo, PrintInstances&& instances); - ~PrintObject() = default; - + ~PrintObject() { if (m_shared_regions && -- m_shared_regions->ref_cnt == 0) delete m_shared_regions; } + void config_apply(const ConfigBase &other, bool ignore_nonexistent = false) { m_config.apply(other, ignore_nonexistent); } void config_apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false) { m_config.apply_only(other, keys, ignore_nonexistent); } PrintBase::ApplyStatus set_instances(PrintInstances &&instances); @@ -308,9 +353,7 @@ private: void ironing(); void generate_support_material(); - void _slice(const std::vector &layer_height_profile); - std::string _fix_slicing_errors(); - void simplify_slices(double distance); + void slice_volumes(); // Has any support (not counting the raft). void detect_surfaces_type(); void process_external_surfaces(); @@ -333,9 +376,9 @@ private: // This is the adjustment of the the Object's coordinate system towards PrintObject's coordinate system. Point m_center_offset; - std::vector> m_all_regions; - // vector of (layer height ranges and vectors of volume ids), indexed by region_id - std::vector m_region_volumes; + // Object split into layer ranges and regions with their associated configurations. + // Shared among PrintObjects created for the same ModelObject. + PrintObjectRegions m_shared_regions; SlicingParameters m_slicing_params; LayerPtrs m_layers; @@ -344,19 +387,6 @@ private: // this is set to true when LayerRegion->slices is split in top/internal/bottom // so that next call to make_perimeters() performs a union() before computing loops bool m_typed_slices = false; - - std::vector slice_region(size_t region_id, const std::vector &z, SlicingMode mode, size_t slicing_mode_normal_below_layer, SlicingMode mode_below) const; - std::vector slice_region(size_t region_id, const std::vector &z, SlicingMode mode) const - { return this->slice_region(region_id, z, mode, 0, mode); } - std::vector slice_modifiers(size_t region_id, const std::vector &z) const; - std::vector slice_volumes( - const std::vector &z, - SlicingMode mode, size_t slicing_mode_normal_below_layer, SlicingMode mode_below, - const std::vector &volumes) const; - std::vector slice_volumes(const std::vector &z, SlicingMode mode, const std::vector &volumes) const - { return this->slice_volumes(z, mode, 0, mode, volumes); } - std::vector slice_volume(const std::vector &z, SlicingMode mode, const ModelVolume &volume) const; - std::vector slice_volume(const std::vector &z, const std::vector &ranges, SlicingMode mode, const ModelVolume &volume) const; }; struct WipeTowerData diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index f0e262fdcd..c23464eb8d 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -242,6 +242,618 @@ static t_config_option_keys full_print_config_diffs(const DynamicPrintConfig &cu return full_config_diff; } +bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const ModelVolumeType type) +{ + size_t i_old, i_new; + for (i_old = 0, i_new = 0; i_old < model_object_old.volumes.size() && i_new < model_object_new.volumes.size();) { + const ModelVolume &mv_old = *model_object_old.volumes[i_old]; + const ModelVolume &mv_new = *model_object_new.volumes[i_new]; + if (mv_old.type() != type) { + ++ i_old; + continue; + } + if (mv_new.type() != type) { + ++ i_new; + continue; + } + if (mv_old.id() != mv_new.id()) + return true; + //FIXME test for the content of the mesh! + + if (!mv_old.get_matrix().isApprox(mv_new.get_matrix())) + return true; + + ++ i_old; + ++ i_new; + } + for (; i_old < model_object_old.volumes.size(); ++ i_old) { + const ModelVolume &mv_old = *model_object_old.volumes[i_old]; + if (mv_old.type() == type) + // ModelVolume was deleted. + return true; + } + for (; i_new < model_object_new.volumes.size(); ++ i_new) { + const ModelVolume &mv_new = *model_object_new.volumes[i_new]; + if (mv_new.type() == type) + // ModelVolume was added. + return true; + } + return false; +} + +// Repository for solving partial overlaps of ModelObject::layer_config_ranges. +// Here the const DynamicPrintConfig* point to the config in ModelObject::layer_config_ranges. +class LayerRanges +{ +public: + struct LayerRange { + t_layer_height_range layer_height_range; + // Config is owned by the associated ModelObject. + const DynamicPrintConfig* config { nullptr }; + }; + + LayerRanges() = default; + + // Convert input config ranges into continuous non-overlapping sorted vector of intervals and their configs. + void assign(const t_layer_config_ranges &in) { + m_ranges.clear(); + m_ranges.reserve(in.size()); + // Input ranges are sorted lexicographically. First range trims the other ranges. + coordf_t last_z = 0; + for (const std::pair &range : in) + if (range.first.second > last_z) { + coordf_t min_z = std::max(range.first.first, 0.); + if (min_z > last_z + EPSILON) { + m_ranges.push_back({ t_layer_height_range(last_z, min_z) }); + last_z = min_z; + } + if (range.first.second > last_z + EPSILON) { + const DynamicPrintConfig *cfg = &range.second.get(); + m_ranges.push_back({ t_layer_height_range(last_z, range.first.second), cfg }); + last_z = range.layer_height_range.second; + } + } + if (m_ranges.empty()) + m_ranges.push_back({ t_layer_height_range(0, DBL_MAX) }); + else if (m_ranges.back().config == nullptr) + m_ranges.back().layer_height_range.second = DBL_MAX; + else + m_ranges.push_back({ t_layer_height_range(m_ranges.back().layer_height_range.second, DBL_MAX) }); + } + + const DynamicPrintConfig* config(const t_layer_height_range &range) const { + auto it = std::lower_bound(m_ranges.begin(), m_ranges.end(), LayerRange { t_layer_height_range(range.first - EPSILON, range.second - EPSILON), nullptr }); + // #ys_FIXME_COLOR + // assert(it != m_ranges.end()); + // assert(it == m_ranges.end() || std::abs(it->first.first - range.first ) < EPSILON); + // assert(it == m_ranges.end() || std::abs(it->first.second - range.second) < EPSILON); + if (it == m_ranges.end() || + std::abs(it->first.first - range.first) > EPSILON || + std::abs(it->first.second - range.second) > EPSILON ) + return nullptr; // desired range doesn't found + return (it == m_ranges.end()) ? nullptr : it->second; + } + std::vector::const_iterator begin() const { return m_ranges.cbegin(); } + std::vector::const_iterator end () const { return m_ranges.cend(); } +private: + // Layer ranges with their config overrides and list of volumes with their snug bounding boxes in a given layer range. + std::vector m_ranges; +}; + +// To track Model / ModelObject updates between the front end and back end, including layer height ranges, their configs, +// and snug bounding boxes of ModelVolumes. +struct ModelObjectStatus { + enum Status { + Unknown, + Old, + New, + Moved, + Deleted, + }; + + enum class PrintObjectRegionsStatus { + Invalid, + Valid, + PartiallyValid, + }; + + ModelObjectStatus(ObjectID id, Status status = Unknown) : id(id), status(status) {} + ~ModelObjectStatus() { if (print_object_regions) print_object_regions.ref_cnt_dec(); } + + // Key of the set. + ObjectID id; + // Status of this ModelObject with id on apply(). + Status status; + // PrintObjects to be generated for this ModelObject including their base transformation. + std::vector print_instances; + // Regions shared by the associated PrintObjects. + PrintObjectRegions *print_object_regions { nullptr }; + // Status of the above. + PrintObjectRegionsStatus print_object_regions_status { PrintObjectRegionsStatus::Invalid }; + + // Search by id. + bool operator<(const ModelObjectStatus &rhs) const { return id < rhs.id; } +}; + +struct ModelObjectStatusDB +{ + void add(const ModelObject &model_object, const ModelObjectStatus status) { + db.emplace(model_object.id(), status); + } + + bool add_if_new(const ModelObject &model_object, const ModelObjectStatus status) { + auto it = db.find(ModelObjectStatus(model_object.id())); + assert(it != db.end()); + if (it == db.end()) { + db.emplace_hint(it, model_object.id(), status); + return true; + } + return false; + } + + const ModelObjectStatus& get(const ModelObject &model_object) { + auto it = db.find(ModelObjectStatus(model_object.id())); + assert(it != db.end()); + return *it; + } + + const ModelObjectStatus& reuse(const ModelObject &model_object) { + const ModelObjectStatus &result = this->get(model_object); + assert(result.status != ModelObjectStatus::Deleted); + return result; + } + + std::set db; +} + +struct PrintObjectStatus { + enum Status { + Unknown, + Deleted, + Reused, + New + }; + PrintObjectStatus(PrintObject *print_object, Status status = Unknown) : + id(print_object->model_object()->id()), + print_object(print_object), + trafo(print_object->trafo()), + status(status) {} + PrintObjectStatus(ObjectID id) : id(id), print_object(nullptr), trafo(Transform3d::Identity()), status(Unknown) {} + // ID of the ModelObject & PrintObject + ObjectID id; + // Pointer to the old PrintObject + PrintObject *print_object; + // Trafo generated with model_object->world_matrix(true) + Transform3d trafo; + Status status; + // Search by id. + bool operator<(const PrintObjectStatus &rhs) const { return id < rhs.id; } +}; + +template +iter_pair make_range(std::pair p) { + return iter_pair(p); +} + +class PrintObjectStatusDB { +public: + using iterator = std::multiset::iterator; + using const_iterator = std::multiset::const_iterator; + + PrintObjectStatusDB(const PrintObjects &print_objects) { + for (PrintObject *print_object : m_objects) + db.emplace(PrintObjectStatus(print_object)); + } + + struct iterator_range : std::pair + { + using std::pair::pair; + + const_iterator begin() throw() { return this->first; } + const_iterator end() throw() { return this->second; } + }; + + iterator_range get_range(const ModelObject &model_object) { + return print_object_status.equal_range(PrintObjectStatus(model_object.id())); + } + + iterator_range get_range(const ModelObjectStatus &model_object_status) { + return print_object_status.equal_range(PrintObjectStatus(model_object_status.id)); + } + + size_t count(const ModelObject &model_object) { + return db.count(PrintObjectStatus(model_object.id())); + } + + void clear() { + db.clear(); + } + + const_iterator begin() throw() { return this->first; } + const_iterator end() throw() { return this->second; } + +private: + std::multiset db; +}; + +static inline bool model_volume_needs_bbox(const ModelVolume &mv) +{ + ModelVolumeType type = mv.type(); + return type == ModelVolumeType::MODEL_PART || type == ModelVolumeType::NEGATIVE_VOLUME || type == ModelVolumeType::PARAMETER_MODIFIER; +} + +static inline Matrix3f trafo_for_bbox(const Transform3d &object_trafo, const Transform3d &volume_trafo) +{ + Matrix3d m = object_trafo.matrix().block<3,3>(0,0) * volume_trafo.matrix().block<3,3>(0,0); + return m.cast(); +} + +static inline bool trafos_differ_in_rotation_and_mirroring_by_z_only(const Transform3d &t1, const Transform3d &t2) +{ + Matrix3d m1 = t1.matrix().block<3, 3>(0, 0); + Matrix3d m2 = t2.matrix().block<3, 3>(0, 0); + Matrix3d m = m2.inverse() * m1; + Vec3d z = m.block<3, 1>(0, 2); + if (std::abs(z.x()) > EPSILON || std::abs(z.y()) > EPSILON || std::abs(z.z() - 1.) > EPSILON) + // Z direction or length changed. + return false; + // Z still points in the same direction and it has the same length. + Vec3d x = m.block<3, 1>(0, 0); + Vec3d y = m.block<3, 1>(0, 1); + if (std::abs(x.z()) > EPSILON || std::abs(y.z()) > EPSILON) + return false; + double lx2 = x.squaredNorm(); + double ly2 = y.squaredNorm(); + if (lx2 - 1. > EPSILON * EPSILON || ly2 - 1. > EPSILON * EPSILON) + return false; + // Verify whether the vectors x, y are still perpendicular. + double d = x.dot(y); + return std::abs(d) > EPSILON; +} + +static BoundingBoxf3 transformed_its_bbox2d(const stl_triangle_vertex_indices &its, const Matrix3f &m) +{ + BoundingBoxf3 bbox; + for (const stl_triangle_vertex_indices &tri : its.indices) + for (int i = 0; i < 3; ++ i) + bbox.merge(m * its.vertices[tri(i)]); + return bbox; +} + +static void transformed_its_bboxes_in_z_ranges( + const stl_triangle_vertex_indices &its, + const Matrix3f &m, + const std::vector &z_ranges, + std::vector &bboxes) +{ + bboxes.assign(z_ranges.size(), BoundingBoxf()); + for (const stl_triangle_vertex_indices &tri : its.indices) { + const Vec3f pts[3] = { m * its.vertices[tri(0)], m * its.vertices[tri(1)], m * its.vertices[tri(2)] }; + for (size_t irange = 0; irange < z_ranges.size(); ++ irange) { + t_layer_height_range &z_range = z_ranges[irange]; + BoundingBoxf3 &bbox = bboxes[irange]; + int iprev = 3; + for (int iedge = 0; iedge < 3; ++ iedge) { + const Vec3f *p1 = &pts[iprev]; + const Vec3f *p2 = &pts[iedge]; + // Sort the edge points by Z. + if (p1->z() > p2->z()) + std::swap(p1, p2); + if (p2->z() <= z_range.first || p1->z() >= z_range.second) { + // Out of this slab. + } else if (p1->z() < z_range.first) { + if (p1->z() > z_range.second) { + // Two intersections. + float zspan = p2->z() - p1->z(); + float t1 = z_range.first / zspan; + float t2 = z_range.second / zspan; + Vec2f p = to_2d(*p1); + Vec2f v(p2->x() - p1->x(), p2->y() - p1->y()); + bbox.merge(to_3d(p + v * t1, z_range.first)); + bbox.merge(to_3d(p + v * t2, z_range.second)); + } else { + // Single intersection with the lower limit. + float t = z_range.first / (p2->z() - p1->z()); + Vec2f v(p2->x() - p1->x(), p2->y() - p1->y()); + bbox.merge(to_3d(to_2d(*p1) + v * t), z_range.first); + bbox.merge(*p2); + } + } else if (p2->z() > z_range.second) { + // Single intersection with the upper limit. + float t = z_range.second / (p2->z() - p1->z()); + Vec2f v(p2->x() - p1->x(), p2->y() - p1->y()); + bbox.merge(to_3d(to_2d(*p1) + v * t), z_range.second); + bbox.merge(*p1); + } else { + // Both points are inside. + bbox.merge(*p1); + bbox.merge(*p2); + } + iprev = iedge; + } + } + } +} + +// Update from scratch. +void update_model_object_status_layers_bboxes( + const ModelObject &model_object, + ModelObjectStatus &model_object_status) +{ + assert(! model_object_status.print_instances.empty()); + assert(! model_object_status.layer_ranges.m_ranges.empty()); + + // Use the trafo of the 1st newly created PrintObject. + model_object_status.layer_ranges.m_trafo = model_object_status.print_instances.front().trafo; + + if (model_object_status.layer_ranges.m_ranges.size() == 1) { + LayerRange &layer_range = model_object_status.layer_ranges.m_ranges.front(); + assert(layer_range.volumes.empty()); + layer_range.reserve(model_object.volumes.size()); + for (const ModelVolume *model_volume : model_object.volumes) + if (model_volume_needs_bbox(*model_volume)) + layer_range.volumes.emplace_back(model_volume->id(), + transformed_its_bbox2d(model_volume.mesh().its, trafo_for_bbox(model_object_status.layer_ranges.m_trafo, model_volume->get_matrix(false)))); + } else { + std::vector bboxes; + std::vector ranges; + ranges.reserve(model_object_status.layer_ranges.m_ranges.size()); + for (const LayerRange &layer_range : model_object_status.layer_ranges.m_ranges) { + assert(layer_range.volumes.empty()); + t_layer_height_range r = layer_range.layer_height_range; + r.first -= EPSILON; + r.second += EPSILON; + ranges.emplace_back(r); + } + double offset = print_object.config().xy_size_compensation(); + for (const ModelVolume *model_volume : model_object.volumes) + if (model_volume_needs_bbox(*model_volume)) { + transformed_its_bbox2ds_in_z_ranges(model_volume.mesh().its, trafo_for_bbox(model_object_status.layer_ranges.m_trafo, model_volume->get_matrix(false)), + ranges, bboxes); + size_t i = 0; + for (LayerRange &layer_range : model_object_status.layer_ranges.m_ranges) { + if (bboxes[i].defined) { + bboxes.min.x() -= offset; + bboxes.min.y() -= offset; + bboxes.max.x() += offset; + bboxes.max.y() += offset; + layer_range.volumes.emplace_back(model_volume->id(), bboxes[i]); + } + ++ i; + } + } +} + +// Update by reusing everything. +void update_model_object_status_layers_bboxes( + const ModelObject &model_object, + PrintObject &&print_object, + ModelObjectStatus &model_object_status) +{ + assert(! model_object_status.print_instances.empty()); + assert(! model_object_status.layer_ranges.m_ranges.empty()); + + for (print_object.m_volumes_ranges +} + +// Try to extract layer ranges and snug bounding boxes from some existing PrintObject. +void update_model_object_status_layers_bboxes( + const ModelObject &model_object_old, + const ModelObject &model_object_new, + PrintObject &&print_object, + ModelObjectStatus &model_object_status) +{ + assert(! print_object.m_region_volumes.empty()); + + // 1) Verify that the trafo is still applicable. + if (trafos_differ_in_rotation_and_mirroring_by_z_only(model_object_status.layer_ranges.m_trafo, model_object_status.print_instances.front().trafo)) { + update_model_object_status_layers_bboxes(model_object_new, model_object_status); + return; + } + + // 2) Try to recover some of the trafos from print_object. + +} + +void print_objects_regions_update_volumes(PrintObjectRegions &print_object_regions, ModelVolumePtrs old_volumes, ModelVolumePtrs new_volumes) +{ + print_object_regions.all_regions.clear(); + + model_volumes_sort_by_id(old_volumes); + model_volumes_sort_by_id(new_volumes); + { + size_t last = 0; + size_t i_old = 0; + for (size_t i_new = 0; i_new < new_volumes.size(); ++ i_new) { + for (; i_old < old_volumes.size(); ++ i_old) + if (old_volumes[i_old]->id() == new_volumes[i_new]->id()) + break; + if (i_old == old_volumes.size()) + break; + if (old_volumes[i_old]->get_matrix().isApprox(new_volumes[i_new]->get_matrix())) { + // Reuse the volume. + new_volumes[last ++] = new_volumes[i_new]; + } else { + // Don't reuse the volume. + } + } + old_volumes.clear(); + new_volumes.erase(new_volumes.begin() + last, new_volumes.end()); + } + + for (PrintObjectRegions::LayerRangeRegions &layer_regions : print_object_regions.layer_ranges) { + auto it = std::remove_if(layer_regions.volumes.begin(), layer_regions.volumes.end(), + [](const PrintObjectRegions::VolumeExtents &v) { return model_volume_find_by_id(new_volumes, v.volume_id) == nullptr; }); + layer_regions.volumes.erase(it, layer_regions.volumes.end()); + layer_regions.volume_regions.clear(); + layer_regions.volume_regions.clear(); + layer_regions.painted_regions.clear(); + } +} + +const BoundingBoxf3* find_volume(const PrintObjectLayerRange &layer_range, const ModelVolume &volume) +{ + auto it = lower_bound_by_predicate(layer_range.volumes.begin(), layer_range.volumes.end(), [&volume](const PrintVolumeExtents &v){ return v.volume_id < volume.id(); }); + retun it != layer_range.volumes.end() && it->volume_id == volume.id() ? &it->bbox : nullptr; +} + +// Returns false if this object needs to be resliced. +static bool verify_update_print_object_regions( + ModelVolumePtrs model_volumes, + const PrintRegionConfig &default_region_config, + size_t num_extruders, + const std::vector &painting_extruders, + const PrintObjectRegions &print_object_regions, + std::function<> callback_invalidate) +{ + // Sort by ModelVolume ID. + std::sort(model_volumes.begin(), model_volumes.end(), [](const ModelVolume *l, const ModelVolume *r){ return l->id() < r->id(); }); + + for (std::unique_ptr ®ion : all_regions) + region->m_ref_cnt = 0; + + // Verify and / or update PrintRegions produced by ModelVolumes, layer range modifiers, modifier volumes. + for (const PrintObjectRegions::LayerRangeRegions &layer_range : print_object_regions.layer_ranges) + for (const PrintObjectRegions::VolumeRegion ®ion : layer_range.volume_regions) { + auto it_model_volume = lower_bound_by_predicate(model_volumes.begin(), model_volumes.end(), [®ion](const ModelVolume *v){ return v->id() < region.volume_id}); + assert(it_model_volume != model_volumes.begin() && it_model_volume->id() == region.volume_id); + PrintRegionConfig cfg = region.parent == -1 ? + PrintObject::region_config_from_model_volume(default_region_config, region.config, *it_model_volume, num_extruders) : + PrintObject::region_config_from_model_volume(parent_region.region->config(), nullptr, *it_model_volume, num_extruders); + if (cfg != region.region->config()) { + // Region configuration changed. + if (region.region->m_ref_cnt == 0) { + // Region is referenced for the first time. Just change its parameters. + // Stop the background process before assigning new configuration to the regions. + t_config_option_keys diff = override.region->config().diff(cfg); + callback_invalidate(override.region->config(), cfg, diff); + override.region->config().config_apply_only(cfg, diff, false); + } else { + // Region is referenced multiple times, thus the region is being split. We need to reslice. + return false; + } + } + ++ region.region->m_ref_cnt; + } + + // Verify and / or update PrintRegions produced by color painting. + for (const PrintObjectRegions::LayerRangeRegions &layer_range : print_object_regions.layer_ranges) + size_t painted_region_idx = 0; + for (unsigned int painted_extruder_id : painting_extruders) + for (int parent_region_id = 0; parent_region_id < int(layer_range.volume_regions.size()); ++ parent_region_id) { + const PrintVolumeRegion &parent_region = layer_range.volume_regions[parent_region_id]; + const PrintPaintedRegion ®ion = layer_range.painted_regions[painted_region_idx ++]; + PrintRegionConfig cfg = parent_region.region->config(); + cfg.external_perimeter_extruder = painted_extruder_id; + cfg.perimeter_extruder = painted_extruder_id; + cfg.infill_extruder = painted_extruder_id; + if (cfg != region.region->config()) { + // Region configuration changed. + if (region->m_ref_cnt == 0) { + // Region is referenced for the first time. Just change its parameters. + // Stop the background process before assigning new configuration to the regions. + t_config_option_keys diff = override.region->config().diff(cfg); + callback_invalidate(override.region->config(), cfg, diff); + override.region->config().config_apply_only(cfg, diff, false); + } else { + // Region is referenced multiple times, thus the region is being split. We need to reslice. + return false; + } + } + ++ region.region->m_ref_cnt; + } + } + + // Lastly verify, whether some regions were not merged. + { + std::vector regions; + regions.reserve(all_regions.size()); + for (std::unique_ptr ®ion : all_regions) { + assert(region->m_ref_cnt > 0); + regions.emplace_back(&(*region)); + } + std::sort(regions.begin(), regions.end(), [](const PrintRegion *l, const PrintRegion *r){ return l->config_hash() < r->config_hash(); }); + for (size_t i = 0; i < regions.size(); ++ i) { + size_t hash = regions[i]->config_hash(); + size_t j = i; + for (++ j; j < regions.size() && regions[j]->config_hash() == hash; ++ j) + if (regions[i]->config() == regions[j]->config()) { + // Regions were merged. We need to reslice. + return false; + } + } + } + + return out; +} + +// Either a fresh PrintObject, or PrintObject regions were invalidated (merged, split). +// Generate PrintRegions from scratch. +static PrintObjectRegions generate_print_object_regions( + const ModelVolumePtrs &model_volumes, + const std::vector &layer_ranges, + const PrintRegionConfig &default_region_config, + size_t num_extruders, + const std::vector &painting_extruders) +{ + PrintObjectRegions out; + auto &all_regions = all_regions; + auto &layer_ranges_regions = layer_ranges; + + layer_ranges_regions.assign(layer_ranges.size(), PrintObjectLayerRangeRegions{}); + for (PrintObjectLayerRangeRegions &lrr : layer_ranges_regions) + lrr = layer_ranges[&lrr - layer_ranges_regions.data()]; + + std::set region_set; + auto get_create_region = [®ion_set](PrintRegionConfig &&config) { + + }; + + // Chain the regions in the order they are stored in the volumes list. + for (int volume_id = 0; volume_id < int(model_volumes.size()); ++ volume_id) { + const ModelVolume &volume = model_volumes[volume_id]; + if (volume.type() == ModelVolumeType::MODEL_PART || volume.type() == ModelVolumeType::PARAMETER_MODIFIER) { + for (const PrintObjectLayerRangeRegions &layer_range : layer_ranges_regions) + if (const BoundingBoxf3 *bbox = find_volume(layer_range, volume); bbox) { + if (volume.type() == MODEL_PART) + layer_range.regions.push_back( { + volume.id(), -1, + get_create_region(PrintObject::region_config_from_model_volume(default_region_config, layer_range.config, volume, num_extruders)) + }); + else { + assert(volume.type() == ModelVolumeType::PARAMETER_MODIFIER); + // Modifiers may be chained one over the other. Check for overlap, merge DynamicPrintConfigs. + for (int parent_region_id = int(layer_range.regions.size()) - 1; parent_region_id >= 0; -- parent_region_id) { + const PrintVolumeRegion &parent_region = layer_range.regions[parent_region_id]; + const BoundingBoxf3 *parent_bbox = find_volume(layer_range, parent_region.volume_id); + assert(parent_bbox != nullptr); + if (parent_bbox->overlap(*bbox)) + layer_range.regions.push_back( { + volume.id(), parent_region_id, + get_create_region(PrintObject::region_config_from_model_volume(parent_region.region->config(), nullptr, volume, num_extruders)) + }); + } + } + } + } + } + + // Finally add painting regions. + for (const PrintObjectLayerRangeRegions &layer_range : layer_ranges_regions) { + for (unsigned int painted_extruder_id : painting_extruders) + for (int parent_region_id = 0; parent_region_id < int(layer_range.regions.size()); ++ parent_region_id) { + const PrintVolumeRegion &parent_region = layer_range.regions[parent_region_id]; + PrintRegionConfig cfg = parent_region.region->config(); + cfg.external_perimeter_extruder = painted_extruder_id; + cfg.perimeter_extruder = painted_extruder_id; + cfg.infill_extruder = painted_extruder_id; + layer_range.painted_regions.push_back({ painted_extruder_id, parent_region_id, get_create_region(std::move(cfg))}); + } + } + + return out; +} + Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_config) { #ifdef _DEBUG @@ -306,70 +918,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ } } - class LayerRanges - { - public: - LayerRanges() {} - // Convert input config ranges into continuous non-overlapping sorted vector of intervals and their configs. - void assign(const t_layer_config_ranges &in) { - m_ranges.clear(); - m_ranges.reserve(in.size()); - // Input ranges are sorted lexicographically. First range trims the other ranges. - coordf_t last_z = 0; - for (const std::pair &range : in) - if (range.first.second > last_z) { - coordf_t min_z = std::max(range.first.first, 0.); - if (min_z > last_z + EPSILON) { - m_ranges.emplace_back(t_layer_height_range(last_z, min_z), nullptr); - last_z = min_z; - } - if (range.first.second > last_z + EPSILON) { - const DynamicPrintConfig *cfg = &range.second.get(); - m_ranges.emplace_back(t_layer_height_range(last_z, range.first.second), cfg); - last_z = range.first.second; - } - } - if (m_ranges.empty()) - m_ranges.emplace_back(t_layer_height_range(0, DBL_MAX), nullptr); - else if (m_ranges.back().second == nullptr) - m_ranges.back().first.second = DBL_MAX; - else - m_ranges.emplace_back(t_layer_height_range(m_ranges.back().first.second, DBL_MAX), nullptr); - } - - const DynamicPrintConfig* config(const t_layer_height_range &range) const { - auto it = std::lower_bound(m_ranges.begin(), m_ranges.end(), std::make_pair< t_layer_height_range, const DynamicPrintConfig*>(t_layer_height_range(range.first - EPSILON, range.second - EPSILON), nullptr)); - // #ys_FIXME_COLOR - // assert(it != m_ranges.end()); - // assert(it == m_ranges.end() || std::abs(it->first.first - range.first ) < EPSILON); - // assert(it == m_ranges.end() || std::abs(it->first.second - range.second) < EPSILON); - if (it == m_ranges.end() || - std::abs(it->first.first - range.first) > EPSILON || - std::abs(it->first.second - range.second) > EPSILON ) - return nullptr; // desired range doesn't found - return (it == m_ranges.end()) ? nullptr : it->second; - } - std::vector>::const_iterator begin() const { return m_ranges.cbegin(); } - std::vector>::const_iterator end() const { return m_ranges.cend(); } - private: - std::vector> m_ranges; - }; - struct ModelObjectStatus { - enum Status { - Unknown, - Old, - New, - Moved, - Deleted, - }; - ModelObjectStatus(ObjectID id, Status status = Unknown) : id(id), status(status) {} - ObjectID id; - Status status; - LayerRanges layer_ranges; - // Search by id. - bool operator<(const ModelObjectStatus &rhs) const { return id < rhs.id; } - }; - std::set model_object_status; + ModelObjectStatusDB model_object_status_db; // 1) Synchronize model objects. if (model.id() != m_model.id()) { @@ -378,14 +927,14 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ this->call_cancel_callback(); update_apply_status(this->invalidate_all_steps()); for (PrintObject *object : m_objects) { - model_object_status.emplace(object->model_object()->id(), ModelObjectStatus::Deleted); + model_object_status_db.add(*object->model_object(), ModelObjectStatus::Deleted); update_apply_status(object->invalidate_all_steps()); delete object; } m_objects.clear(); m_model.assign_copy(model); for (const ModelObject *model_object : m_model.objects) - model_object_status.emplace(model_object->id(), ModelObjectStatus::New); + model_object_status_db.add(*model_object, ModelObjectStatus::New); } else { if (m_model.custom_gcode_per_print_z != model.custom_gcode_per_print_z) { update_apply_status(num_extruders_changed || @@ -402,14 +951,14 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ if (model_object_list_equal(m_model, model)) { // The object list did not change. for (const ModelObject *model_object : m_model.objects) - model_object_status.emplace(model_object->id(), ModelObjectStatus::Old); + model_object_status_db.add(*model_object, ModelObjectStatus::Old); } else if (model_object_list_extended(m_model, model)) { // Add new objects. Their volumes and configs will be synchronized later. update_apply_status(this->invalidate_step(psGCodeExport)); for (const ModelObject *model_object : m_model.objects) - model_object_status.emplace(model_object->id(), ModelObjectStatus::Old); + model_object_status_db.add(*model_object, ModelObjectStatus::Old); for (size_t i = m_model.objects.size(); i < model.objects.size(); ++ i) { - model_object_status.emplace(model.objects[i]->id(), ModelObjectStatus::New); + model_object_status_db.add(*model.objects[i], ModelObjectStatus::New); m_model.objects.emplace_back(ModelObject::new_copy(*model.objects[i])); m_model.objects.back()->set_model(&m_model); } @@ -430,31 +979,28 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ // New ModelObject added. m_model.objects.emplace_back(ModelObject::new_copy(*mobj)); m_model.objects.back()->set_model(&m_model); - model_object_status.emplace(mobj->id(), ModelObjectStatus::New); + model_object_status_db.add(*mobj, ModelObjectStatus::New); } else { // Existing ModelObject re-added (possibly moved in the list). m_model.objects.emplace_back(*it); - model_object_status.emplace(mobj->id(), ModelObjectStatus::Moved); + model_object_status_db.add(*mobj, ModelObjectStatus::Moved); } } bool deleted_any = false; - for (ModelObject *&model_object : model_objects_old) { - if (model_object_status.find(ModelObjectStatus(model_object->id())) == model_object_status.end()) { - model_object_status.emplace(model_object->id(), ModelObjectStatus::Deleted); + for (ModelObject *&model_object : model_objects_old) + if (model_object_status_db.add_if_new(*model_object, ModelObjectStatus::Deleted)) deleted_any = true; - } else + else // Do not delete this ModelObject instance. model_object = nullptr; - } if (deleted_any) { // Delete PrintObjects of the deleted ModelObjects. PrintObjectPtrs print_objects_old = std::move(m_objects); m_objects.clear(); m_objects.reserve(print_objects_old.size()); for (PrintObject *print_object : print_objects_old) { - auto it_status = model_object_status.find(ModelObjectStatus(print_object->model_object()->id())); - assert(it_status != model_object_status.end()); - if (it_status->status == ModelObjectStatus::Deleted) { + const ModelObjectStatus &status = model_object_status_db.get(*print_object->model_object()); + if (status.status == ModelObjectStatus::Deleted) { update_apply_status(print_object->invalidate_all_steps()); delete print_object; } else @@ -467,41 +1013,14 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ } // 2) Map print objects including their transformation matrices. - struct PrintObjectStatus { - enum Status { - Unknown, - Deleted, - Reused, - New - }; - PrintObjectStatus(PrintObject *print_object, Status status = Unknown) : - id(print_object->model_object()->id()), - print_object(print_object), - trafo(print_object->trafo()), - status(status) {} - PrintObjectStatus(ObjectID id) : id(id), print_object(nullptr), trafo(Transform3d::Identity()), status(Unknown) {} - // ID of the ModelObject & PrintObject - ObjectID id; - // Pointer to the old PrintObject - PrintObject *print_object; - // Trafo generated with model_object->world_matrix(true) - Transform3d trafo; - Status status; - // Search by id. - bool operator<(const PrintObjectStatus &rhs) const { return id < rhs.id; } - }; - std::multiset print_object_status; - for (PrintObject *print_object : m_objects) - print_object_status.emplace(PrintObjectStatus(print_object)); + PrintObjectStatusDB print_object_status_db(m_objects); // 3) Synchronize ModelObjects & PrintObjects. for (size_t idx_model_object = 0; idx_model_object < model.objects.size(); ++ idx_model_object) { - ModelObject &model_object = *m_model.objects[idx_model_object]; - auto it_status = model_object_status.find(ModelObjectStatus(model_object.id())); - assert(it_status != model_object_status.end()); - assert(it_status->status != ModelObjectStatus::Deleted); - const ModelObject& model_object_new = *model.objects[idx_model_object]; - const_cast(*it_status).layer_ranges.assign(model_object_new.layer_config_ranges); + ModelObject &model_object = *m_model.objects[idx_model_object]; + ModelObjectStatus &model_object_status = const_cast(model_object_status.reuse(model_object)); + const ModelObject &model_object_new = *model.objects[idx_model_object]; + model_object_status.print_instances = print_objects_from_model_object(*model_object_new); if (it_status->status == ModelObjectStatus::New) // PrintObject instances will be added in the next loop. continue; @@ -513,16 +1032,28 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ bool modifiers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::PARAMETER_MODIFIER); bool supports_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_BLOCKER) || model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_ENFORCER); + bool layer_height_ranges_differ = ! layer_height_ranges_equal(model_object.layer_config_ranges, model_object_new.layer_config_ranges, model_object_new.layer_height_profile.empty()); + auto print_objects_range = print_object_status_db.get_range(&model_object); + assert(print_objects_range.begin() != print_objects_range.end()); + model_object_status.prints_objects_regions = (*print_objects_range.begin())->prints_objects_regions; + model_object_status.prints_objects_regions->ref_cnt_inc(); if (model_parts_differ || modifiers_differ || model_object.origin_translation != model_object_new.origin_translation || ! model_object.layer_height_profile.timestamp_matches(model_object_new.layer_height_profile) || - ! layer_height_ranges_equal(model_object.layer_config_ranges, model_object_new.layer_config_ranges, model_object_new.layer_height_profile.empty())) { + layer_height_ranges_differ) { // The very first step (the slicing step) is invalidated. One may freely remove all associated PrintObjects. - auto range = print_object_status.equal_range(PrintObjectStatus(model_object.id())); - for (auto it = range.first; it != range.second; ++ it) { + model_object_status.prints_objects_regions_status = + model_object.origin_translation == model_object_new.origin_translation && ! layer_height_ranges_differ ? + // Drop print_objects_regions. + ModelObjectStatus::PrintObjectRegionsStatus::Invalid : + // Reuse bounding boxes of print_objects_regions for ModelVolumes with unmodified transformation. + ModelObjectStatus::PrintObjectRegionsStatus::PartiallyValid; + for (auto it : print_objects_range) { update_apply_status(it->print_object->invalidate_all_steps()); const_cast(*it).status = PrintObjectStatus::Deleted; } + if (model_object_status.prints_objects_regions_status == ModelObjectStatus::PrintObjectRegionsStatus::PartiallyValid) + print_objects_regions_update_volumes(*model_object_status.prints_objects_regions, model_object.volumes, model_object_new.volumes); // Copy content of the ModelObject including its ID, do not change the parent. model_object.assign_copy(model_object_new); } else if (supports_differ || model_custom_supports_data_changed(model_object, model_object_new)) { @@ -532,8 +1063,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ update_apply_status(false); } // Invalidate just the supports step. - auto range = print_object_status.equal_range(PrintObjectStatus(model_object.id())); - for (auto it = range.first; it != range.second; ++ it) + for (auto it : print_objects_range) update_apply_status(it->print_object->invalidate_step(posSupportMaterial)); if (supports_differ) { // Copy just the support volumes. @@ -549,8 +1079,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ model_object.config.assign_config(model_object_new.config); if (! object_diff.empty() || object_config_changed || num_extruders_changed) { PrintObjectConfig new_config = PrintObject::object_config_from_model_object(m_default_object_config, model_object, num_extruders); - auto range = print_object_status.equal_range(PrintObjectStatus(model_object.id())); - for (auto it = range.first; it != range.second; ++ it) { + for (auto it : print_object_status_db.get_range(model_object)) { t_config_option_keys diff = it->print_object->config().diff(new_config); if (! diff.empty()) { update_apply_status(it->print_object->invalidate_state_by_config_options(it->print_object->config(), new_config, diff)); @@ -602,14 +1131,12 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ bool new_objects = false; // Walk over all new model objects and check, whether there are matching PrintObjects. for (ModelObject *model_object : m_model.objects) { - auto range = print_object_status.equal_range(PrintObjectStatus(model_object->id())); + ModelObjectStatus &model_object_status = const_cast(model_object_status.reuse(model_object)); std::vector old; - if (range.first != range.second) { - old.reserve(print_object_status.count(PrintObjectStatus(model_object->id()))); - for (auto it = range.first; it != range.second; ++ it) - if (it->status != PrintObjectStatus::Deleted) - old.emplace_back(&(*it)); - } + old.reserve(print_object_status.count(*model_object)); + for (auto it : print_object_status.get_range(*model_object)) + if (it->status != PrintObjectStatus::Deleted) + old.emplace_back(&(*it)); // Generate a list of trafos and XY offsets for instances of a ModelObject // Producing the config for PrintObject on demand, caching it at print_object_last. const PrintObject *print_object_last = nullptr; @@ -619,10 +1146,9 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ PrintObject::object_config_from_model_object(m_default_object_config, *model_object, num_extruders)); print_object_last = print_object; }; - std::vector new_print_instances = print_objects_from_model_object(*model_object); if (old.empty()) { // Simple case, just generate new instances. - for (PrintObjectTrafoAndInstances &print_instances : new_print_instances) { + for (PrintObjectTrafoAndInstances &print_instances : model_object_status.print_instances) { PrintObject *print_object = new PrintObject(this, model_object, print_instances.trafo, std::move(print_instances.instances)); print_object_apply_config(print_object); print_objects_new.emplace_back(print_object); @@ -636,7 +1162,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ std::sort(old.begin(), old.end(), [](const PrintObjectStatus *lhs, const PrintObjectStatus *rhs){ return transform3d_lower(lhs->trafo, rhs->trafo); }); // Merge the old / new lists. auto it_old = old.begin(); - for (PrintObjectTrafoAndInstances &new_instances : new_print_instances) { + size_t num_old = old.size(); + for (PrintObjectTrafoAndInstances &new_instances : model_object_status.print_instances) { for (; it_old != old.end() && transform3d_lower((*it_old)->trafo, new_instances.trafo); ++ it_old); if (it_old == old.end() || ! transform3d_equal((*it_old)->trafo, new_instances.trafo)) { // This is a new instance (or a set of instances with the same trafo). Just add it. @@ -645,8 +1172,11 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ print_objects_new.emplace_back(print_object); // print_object_status.emplace(PrintObjectStatus(print_object, PrintObjectStatus::New)); new_objects = true; - if (it_old != old.end()) + if (it_old != old.end()) { + if (-- num_old == 0) + update_model_object_status_layers_bboxes(*model_object, model_object_status, std::move(*old.front()->print_object)); const_cast(*it_old)->status = PrintObjectStatus::Deleted; + } } else { // The PrintObject already exists and the copies differ. PrintBase::ApplyStatus status = (*it_old)->print_object->set_instances(std::move(new_instances.instances)); @@ -663,7 +1193,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ m_objects = print_objects_new; // Delete the PrintObjects marked as Unknown or Deleted. bool deleted_objects = false; - for (auto &pos : print_object_status) + for (auto &pos : print_object_status_db) if (pos.status == PrintObjectStatus::Unknown || pos.status == PrintObjectStatus::Deleted) { update_apply_status(pos.print_object->invalidate_all_steps()); delete pos.print_object; @@ -679,117 +1209,71 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ } // All regions now have distinct settings. - // Check whether applying the new region config defaults we'd get different regions. - for (PrintObject *print_object : m_objects) { - const LayerRanges *layer_ranges; - { - auto it_status = model_object_status.find(ModelObjectStatus(print_object->model_object()->id())); - assert(it_status != model_object_status.end()); - assert(it_status->status != ModelObjectStatus::Deleted); - layer_ranges = &it_status->layer_ranges; - } - bool some_object_region_modified = false; - bool regions_merged = false; - for (size_t region_id = 0; region_id < print_object->m_region_volumes.size(); ++ region_id) { - PrintRegion ®ion = *print_object->m_all_regions[region_id]; - PrintRegionConfig region_config; - bool region_config_set = false; - for (const PrintRegionVolumes::VolumeWithZRange &volume_w_zrange : print_object->m_region_volumes[region_id].volumes) { - const ModelVolume &volume = *print_object->model_object()->volumes[volume_w_zrange.volume_idx]; - const DynamicPrintConfig *layer_range_config = layer_ranges->config(volume_w_zrange.layer_height_range); - PrintRegionConfig this_region_config = PrintObject::region_config_from_model_volume(m_default_region_config, layer_range_config, volume, num_extruders); - if (region_config_set) { - if (this_region_config != region_config) { - regions_merged = true; - break; + // Check whether applying the new region config defaults we would get different regions. + const std::vector painting_extruders; + for (auto it_print_object = m_objects.begin(); it_print_object != m_objects.end();) { + // Find the range of PrintObjects sharing the same associated ModelObject. + auto it_print_object_end = m_objects.begin(); + PrintObject &print_object = *(*it_print_object); + const ModelObject &model_object = *print_object.model_object(); + ModelObjectStatus &model_object_status = const_cast(model_object_status.reuse(model_object)); + PrintObjectRegions *print_object_regions = model_object_status.print_object_regions; + for (++ it_print_object_end; it_print_object != m_objects.end() && (*it_print_object)->model_object() == (*it_print_object_end)->model_object(); ++ it_print_object_end) + assert((*it_print_object_end)->m_shared_regions == nullptr || (*it_print_object_end)->m_shared_regions == print_object_regions); + if (print_object_regions == nullptr) + print_object_regions = new PrintObjectRegions {}; + if (model_object_status.print_object_regions_status == ModelObjectStatus::PrintObjectRegionsStatus::Valid) { + if (verify_update_print_object_regions( + print_object.model_object()->volumes, + m_default_region_config, + num_extruders, + painting_extruders, + *print_object_regions, + [](){ + // Stop the background process before assigning new configuration to the regions. + t_config_option_keys diff = region.config().diff(region_config); + update_apply_status(print_object->invalidate_state_by_config_options(region.config(), region_config, diff)); + region.config_apply_only(region_config, diff, false); + })) { + // Regions are valid, assign them to the other PrintObjects. + for (auto it = it_print_object; it != it_print_object_end; ++ it) + if ((*it)->m_shared_regions == nullptr) { + (*it)->m_shared_regions = print_object_regions; + print_object_regions->ref_cnt_inc(); } - } else { - region_config = std::move(this_region_config); - region_config_set = true; - } - } - if (regions_merged) - break; - size_t region_config_hash = region_config.hash(); - bool modified = region.config_hash() != region_config_hash || region.config() != region_config; - some_object_region_modified |= modified; - if (some_object_region_modified) - // Verify whether this region was not merged with some other region. - for (size_t i = 0; i < region_id; ++ i) { - const PrintRegion ®ion_other = *print_object->m_all_regions[i]; - if (region_other.config_hash() == region_config_hash && region_other.config() == region_config) { - // Regions were merged. Reset this print_object. - regions_merged = true; - break; + } else { + // Regions were reshuffled. + for (auto it = it_print_object; it != it_print_object_end; ++ it) + if ((*it)->m_shared_regions != nullptr) { + assert(print_object_regions == (*it)->m_shared_regions); + update_apply_status((*it)->invalidate_all_steps()); + (*it)->m_shared_regions = nullptr; + print_object_regions->ref_cnt_dec(); } - } - if (modified) { - // Stop the background process before assigning new configuration to the regions. - t_config_option_keys diff = region.config().diff(region_config); - update_apply_status(print_object->invalidate_state_by_config_options(region.config(), region_config, diff)); - region.config_apply_only(region_config, diff, false); - } - } - if (regions_merged) { - // Two regions of a single object were either split or merged. This invalidates the whole slicing. - update_apply_status(print_object->invalidate_all_steps()); - print_object->m_region_volumes.clear(); - } - } - - // Possibly add new regions for the newly added or resetted PrintObjects. - for (size_t idx_print_object = 0; idx_print_object < m_objects.size();) { - PrintObject &print_object0 = *m_objects[idx_print_object]; - const ModelObject &model_object = *print_object0.model_object(); - const LayerRanges *layer_ranges; - { - auto it_status = model_object_status.find(ModelObjectStatus(model_object.id())); - assert(it_status != model_object_status.end()); - assert(it_status->status != ModelObjectStatus::Deleted); - layer_ranges = &it_status->layer_ranges; - } - if (print_object0.m_region_volumes.empty()) { - // Fresh or completely invalidated print_object. Assign regions. - unsigned int volume_id = 0; - for (const ModelVolume *volume : model_object.volumes) { - if (! volume->is_model_part() && ! volume->is_modifier()) { - ++ volume_id; - continue; - } - // Filter the layer ranges, so they do not overlap and they contain at least a single layer. - // Now insert a volume with a layer range to its own region. - for (auto it_range = layer_ranges->begin(); it_range != layer_ranges->end(); ++ it_range) { - int region_id = -1; - // Get the config applied to this volume. - PrintRegionConfig config = PrintObject::region_config_from_model_volume(m_default_region_config, it_range->second, *volume, num_extruders); - size_t hash = config.hash(); - for (size_t i = 0; i < print_object0.m_all_regions.size(); ++ i) - if (hash == print_object0.m_all_regions[i]->config_hash() && config == *print_object0.m_all_regions[i]) { - region_id = int(i); - break; - } - // If no region exists with the same config, create a new one. - if (region_id == -1) { - region_id = int(print_object0.m_all_regions.size()); - print_object0.m_all_regions.emplace_back(std::make_unique(std::move(config), hash)); - } - print_object0.add_region_volume(region_id, volume_id, it_range->first); - } - ++ volume_id; - } - print_regions_reshuffled = true; - } - for (++ idx_print_object; idx_print_object < m_objects.size() && m_objects[idx_print_object]->model_object() == &model_object; ++ idx_print_object) { - PrintObject &print_object = *m_objects[idx_print_object]; - if (print_object.m_region_volumes.empty()) { - // Copy region volumes and regions from print_object0. - print_object.m_region_volumes = print_object0.m_region_volumes; - print_object.m_all_regions.reserve(print_object0.m_all_regions.size()); - for (const std::unique_ptr ®ion : print_object0.m_all_regions) - print_object.m_all_regions.emplace_back(std::make_unique(*region)); + // At least reuse layer ranges and bounding boxes of ModelVolumes. + model_object_status.print_object_regions_status = ModelObjectStatus::PrintObjectRegionsStatus::PartiallyValid; print_regions_reshuffled = true; } } + if (model_object_status.print_object_regions_status == ModelObjectStatus::PrintObjectRegionsStatus::PartiallyValid) { + // Reuse layer ranges and bounding boxes of ModelVolumes. + } else { + assert(model_object_status.print_object_regions_status == ModelObjectStatus::PrintObjectRegionsStatus::Invalid); + // Layer ranges with their associated configurations. + LayerRanges layer_ranges; + layer_ranges.assign(print_object.model_object()->layer_config_ranges); + print_object_regions = new PrintObjectRegions(generate_print_object_regions( + print_object.model_object()->volumes, + layer_ranges, + m_default_region_config, + num_extruders, + painting_extruders)); + for (auto it = it_print_object; it != it_print_object_end; ++ it) { + (*it)->m_shared_regions = print_object_regions; + print_object_regions->ref_cnt_inc(); + } + } + it_print_object = it_print_object_end; } if (print_regions_reshuffled) { @@ -797,15 +1281,19 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ struct cmp { bool operator() (const PrintRegion *l, const PrintRegion *r) const { return l->config_hash() == r->config_hash() && l->config() == r->config(); } }; std::set region_set; m_print_regions.clear(); - for (PrintObject *print_object : m_objects) - for (std::unique_ptr &print_region : print_object->m_all_regions) - if (auto it = region_set.find(print_region.get()); it == region_set.end()) { - int print_region_id = int(m_print_regions.size()); - m_print_regions.emplace_back(print_region.get()); - print_region->m_print_region_id = print_region_id; - } else { - print_region->m_print_region_id = (*it)->print_region_id(); - } + PrintObjectRegions *print_object_regions = nullptr; + for (PrintObject *print_object : m_objects) { + if (print_object_regions != print_object->m_shared_regions) { + print_object_regions = print_object->m_shared_regions; + for (std::unique_ptr &print_region : print_object_regions->all_regions) + if (auto it = region_set.find(print_region.get()); it == region_set.end()) { + int print_region_id = int(m_print_regions.size()); + m_print_regions.emplace_back(print_region.get()); + print_region->m_print_region_id = print_region_id; + } else { + print_region->m_print_region_id = (*it)->print_region_id(); + } + } } // Update SlicingParameters for each object where the SlicingParameters is not valid. diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp new file mode 100644 index 0000000000..f96cebed38 --- /dev/null +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -0,0 +1,914 @@ +#include "Print.hpp" +#include "I18N.hpp" + +#include + +#include + +//! macro used to mark string used at localization, return same string +#define L(s) Slic3r::I18N::translate(s) + +namespace Slic3r { + +static inline LayerPtrs new_layers( + PrintObject *print_object, + // Object layers (pairs of bottom/top Z coordinate), without the raft. + const std::vector &object_layers, + // Reserve object layers for the raft. Last layer of the raft is the contact layer. + size_t first_layer_id) +{ + LayerPtrs out; + out.reserve(object_layers.size()); + auto id = int(first_layer_id); + Layer *prev = nullptr; + for (size_t i_layer = 0; i_layer < object_layers.size(); i_layer += 2) { + coordf_t lo = object_layers[i_layer]; + coordf_t hi = object_layers[i_layer + 1]; + coordf_t slice_z = 0.5 * (lo + hi); + Layer *layer = new Layer(id ++, print_object, hi - lo, hi + m_slicing_params.object_print_z_min, slice_z); + out.emplace_back(layer); + if (prev != nullptr) { + prev->upper_layer = layer; + layer->lower_layer = prev; + } + prev = layer; + } + return out; +} + +template +static inline std::vector zs_from_layers(const LayerContainer &layers) +{ + std::vector zs; + zs.reserve(layers.size()); + for (const Layer *l : layers) + zs.emplace_back((float)l->slice_z); + return zs; +} + +//FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it. +static void fix_mesh_connectivity(TriangleMesh &mesh) +{ + auto nr_degenerated = mesh.stl.stats.degenerate_facets; + stl_check_facets_exact(&mesh.stl); + if (nr_degenerated != mesh.stl.stats.degenerate_facets) + // stl_check_facets_exact() removed some newly degenerated faces. Some faces could become degenerate after some mesh transformation. + stl_generate_shared_vertices(&mesh.stl, mesh.its); +} + +struct SliceVolumeParams +{ + const SlicingMode mode { SlicingMode::Regular }; + // For vase mode: below this layer a different slicing mode will be used to produce a single contour. + // 0 = ignore. + const size_t slicing_mode_normal_below_layer { 0 }; + // Mode to apply below slicing_mode_normal_below_layer. Ignored if slicing_mode_nromal_below_layer == 0. + const SlicingMode mode_below { SlicingMode::Regular }; + + // Morphological closing operation when creating output expolygons. + const float closing_radius { 0 }; + // Positive offset applied when creating output expolygons. + const float extra_offset { 0 }; + // Resolution for contour simplification. + // 0 = don't simplify. + const double resolution { 0 }; + + // Transformation of the object owning the ModelVolume. + Transform3d object_trafo; +}; + +// Slice single triangle mesh. +static std::vector slice_volume( + const ModelVolume &volume, + const std::vector &z, + const SliceVolumeParams ¶ms, + const TriangleMeshSlicer::throw_on_cancel_callback_type &throw_on_cancel_callback) +{ + std::vector layers; + if (! z.empty()) { + TriangleMesh mesh(volume.mesh()); + mesh.transform(params.object_trafo * volume.get_matrix(), true); + if (mesh.repaired) + fix_mesh_connectivity(mesh); + if (mesh.stl.stats.number_of_facets > 0) { + // perform actual slicing + TriangleMeshSlicer mesh_slicer; + // TriangleMeshSlicer needs the shared vertices. + mesh.require_shared_vertices(); + mesh_slicer.init(&mesh, throw_on_cancel_callback); + //FIXME simplify contours + mesh_slicer.slice(z, mode, params.slicing_mode_normal_below_layer, params.mode_below, params.closing_radius, params.extra_offset, &layers, throw_on_cancel_callback); + throw_on_cancel_callback(); + } + } + return layers; +} + +// Slice single triangle mesh. +// Filter the zs not inside the ranges. The ranges are closed at the bottom and open at the top, they are sorted lexicographically and non overlapping. +static std::vector slice_volume( + const ModelVolume &volume, + const std::vector &z, + const std::vector &ranges, + const SliceVolumeParams ¶ms, + const TriangleMeshSlicer::throw_on_cancel_callback_type &throw_on_cancel_callback) +{ + std::vector out; + if (! z.empty() && ! ranges.empty()) { + if (ranges.size() == 1 && z.front() >= ranges.front().first && z.back() < ranges.front().second) { + // All layers fit into a single range. + out = slice_volume(volume, z, params, throw_on_cancel_callback); + } else { + std::vector z_filtered; + std::vector> n_filtered; + z_filtered.reserve(z.size()); + n_filtered.reserve(2 * ranges.size()); + size_t i = 0; + for (const t_layer_height_range &range : ranges) { + for (; i < z.size() && z[i] < range.first; ++ i) ; + size_t first = i; + for (; i < z.size() && z[i] < range.second; ++ i) + z_filtered.emplace_back(z[i]); + if (i > first) + n_filtered.emplace_back(std::make_pair(first, i)); + } + if (! n_filtered.empty()) { + std::vector layers = slice_volume(volume, z_filtered, params, throw_on_cancel_callback); + out.assign(z.size(), ExPolygons()); + i = 0; + for (const std::pair &span : n_filtered) + for (size_t j = span.first; j < span.second; ++ j) + out[j] = std::move(layers[i ++]); + } + } + } + return out; +} + +struct VolumeSlices +{ + ObjectID volume_id; + std::vector slices; +}; + +static inline bool model_volume_needs_slicing(const ModelVolume &mv) +{ + ModelVolumeType type = mv.type(); + return type == ModelVolumeType::MODEL_PART || type == ModelVolumeType::NEGATIVE_VOLUME || type == ModelVolumeType::PARAMETER_MODIFIER; +} + +// Slice printable volumes, negative volumes and modifier volumes, sorted by ModelVolume::id(). +// Apply closing radius. +// Apply positive XY compensation to ModelVolumeType::MODEL_PART and ModelVolumeType::PARAMETER_MODIFIER, not to ModelVolumeType::NEGATIVE_VOLUME. +// Apply contour simplification. +static std::vector slice_volumes( + const PrintConfig &print_config, + const PrintObjectConfig &print_object_config, + const Transform3d &object_trafo, + ModelVolumePtrs model_volumes, + const std::vector &layer_ranges; + const std::vector &zs, + const TriangleMeshSlicer::throw_on_cancel_callback_type &throw_on_cancel_callback) +{ + model_volumes_sort_by_id(model_volumes); + + std::vector out; + out.reserve(model_volumes.size()); + + std::vector slicing_ranges; + if (layer_ranges.size() > 1) + slicing_ranges.reserve(layer_ranges.size()); + + SliceVolumeParams params_base; + params_base.closing_radius = float(print_object_config.slice_closing_radius.value); + params_base.extra_offset = 0; + params_base.object_trafo = object_trafo; + params_base.resolution = print_config.resolution; + + const float extra_offset = print_object_config.xy_size_compensation > 0 ? float(print_object_config.xy_size_compensation.value) : 0.f; + + for (const ModelVolume *model_volume : model_volumes) + if (model_volume_needs_slicing(*model_volume)) { + SliceVolumeParams params { params_base }; + if (! model_volume->is_negative_volume()) + params.extra_offset = extra_ofset; + if (layer_ranges.size() == 1) { + if (const PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges.front(); layer_range.has_volume(model_volume->id())) { + if (model_volume->is_model_part() && print_config.spiral_vase) { + auto it = std::find_first(layer_range.volume_regions.begin(), layer_range.volume_regions.end(), + [model_volume](const auto &slice){ return model_volume == slice.model_volume; }); + params.mode = SlicingMode::PositiveLargestContour; + // Slice the bottom layers with SlicingMode::Regular. + // This needs to be in sync with LayerRegion::make_perimeters() spiral_vase! + params.mode_below = SlicingMode::Regular; + const PrintRegionConfig ®ion_config = it->region->config(); + slicing_mode_normal_below_layer = size_t(region_config.bottom_solid_layers.value); + for (; slicing_mode_normal_below_layer < zs.size() && zs[slicing_mode_normal_below_layer] < region_config.bottom_solid_min_thickness - EPSILON; + ++ slicing_mode_normal_below_layer); + } + out.push_back({ + model_volume->id(), + slice_volume(*model_volume, zs, params, throw_on_cancel_callback) + }); + } + } else { + assert(! spiral_vase); + slicing_ranges.clear(); + for (const PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges) + if (layer_range.has_volume(model_volume->id())) + slicing_ranges.emplace_back(layer_range.layer_height_range); + if (! slicing_ranges.empty()) + out.push_back({ + model_volume->id(), + slice_volume(*model_volume, zs, slicing_ranges, params, throw_on_cancel_callback) + }); + } + if (! out.empty() && out.back().slices.empty()) + out.pop_back(); + } +} + +static inline VolumeSlices& volume_slices_find_by_id(std::vector &volume_slices, const ObjectID id) +{ + auto it = lower_bound_by_predicate(volume_slices.begin(), volume_slices.end(), [id](const VolumeSlices &vs) { return vs.volume_id < id; }); + assert(it != volume_slices.end() && it->volume_id == id); + return *it; +} + +static inline bool overlap_in_xy(const BoundingBoxf3 &l, const BoundingBoxf3 &r) +{ + return ! (l.max.x() < r.min.x() || l.min.x() > r.max.x() || + l.max.y() < r.min.y() || l.min.y() > r.max.y()); +} + +static std::vector> slices_to_regions( + ModelVolumePtrs model_volumes, + const PrintObjectRegions &print_object_regions, + const std::vector &zs, + std::vector &&volume_slices, + // If clipping is disabled, then ExPolygons produced by different volumes will never be merged, thus they will be allowed to overlap. + // It is up to the model designer to handle these overlaps. + const bool clip_multipart_objects, + const TriangleMeshSlicer::throw_on_cancel_callback_type &throw_on_cancel_callback) +{ + model_volumes_sort_by_id(model_volumes); + + std::vector> slices_by_region(print_object_regions.all_regions.size(), std::vector(zs.size(), ExPolygons())); + + // First shuffle slices into regions if there is no overlap with another region possible, collect zs of the complex cases. + std::vector zs_complex; + { + size_t z_idx = 0; + for (const PrintObjectRegions::LayerRangeRegions &layer_range : print_object_regions.layer_ranges) { + for (; z_idx < zs.size() && zs[z_idx] < layer_range.layer_height_range.first; ++ z_idx) ; + if (layer_range.volume_regions.empty()) { + } else if (layer_range.volume_regions.size() == 1) { + const ModelVolume *model_volume = layer_range.volume_regions.model_volume; + assert(model_volume != nullptr); + if (model_volume->is_model_part()) { + VolumeSlices &slices_src = volume_slices_find_by_id(volume_slices, model_volume->id()); + auto &slices_dst = slices_by_region[layer_range.volume_regions.front().region->print_object_region_id()]; + for (; z_idx < zs.size() && zs[z_idx] < layer_range.layer_height_range.second; ++ z_idx) + slices_dst[z_idx] = std::move(slices_src[z_idx]); + } + } else { + zs_complex.reserve(zs.size()); + for (; z_idx < zs.size() && zs[z_idx] < layer_range.layer_height_range.second; ++ z_idx) { + float z = zs[z_idx]; + int idx_first_printable_region = -1; + bool complex = false; + for (int idx_region = 0; idx_region < int(layer_range.volume_regions.size()); ++ idx_region) { + const PrintObjectRegions::VolumeRegion ®ion = layer_range.volume_regions[idx_region]; + if (region.bbox.min().z() >= z && region.bbox.max().z() <= z) { + if (idx_first_printable_region == -1 && region.model_volume->is_model_part()) + idx_first_printable_region = idx_region; + else if (idx_first_printable_region != -1) { + // Test for overlap with some other region. + for (int idx_region2 = idx_first_printable_region; idx_region2 < idx_region; ++ idx_region2) { + const PrintObjectRegions::VolumeRegion ®ion2 = layer_range.volume_regions[idx_region2]; + if (region2.bbox.min().z() >= z && region2.bbox.max().z() <= z && overlap_in_xy(*region.bbox, *region2.bbox)) { + complex = true; + break; + } + } + } + } + } + if (complex) + zs_complex.emplace_back(z); + else if (idx_first_printable_region) { + const PrintObjectRegions::VolumeRegion ®ion = layer_range.volume_regions[idx_first_printable_region]; + slices_by_region[region.region->print_object_region_id()][z_idx] = std::move(volume_slices_find_by_id(volume_slices, region.model_volume->id()).slices[z_idx]); + } + } + } + throw_on_cancel_callback(); + } + } + + // Second perform region clipping and assignment in parallel. + if (! zs_complex.empty()) { + struct SliceEntry { + VolumeSlices* volume_slices; + int prev_same_region { -1 }; + }; + std::vector> layer_ranges_regions_to_slices(print_object_regions.layer_ranges.size(), std::vector()); + std::vector last_volume_idx_of_region; + for (PrintObjectRegions::LayerRangeRegions &layer_range : print_object_regions.layer_ranges) { + std::vector &layer_range_regions_to_slices = layer_ranges_regions_to_slices[&layer_range - print_object_regions.layer_ranges.data()]; + layer_range_regions_to_slices.reserve(layer_range.volume_regions.size()); + last_volume_idx_of_region.assign(print_object_regions.layer_ranges.all_regions.size(), -1); + for (PrintObjectRegions::VolumeRegion ®ion : layer_range.volume_regions) { + int region_id = region.region->print_object_region_id(); + layer_range_regions_to_slices.emplace_back({ &volume_slices_find_by_id(volume_slices, region.model_volume->id()), last_volume_idx_of_region[region_id] }); + last_volume_idx_of_region[region_id] = ®ion - layer_range.volume_regions.data(); + } + } + tbb::parallel_for( + tbb::blocked_range(0, zs_complex.size()), + [&slices_by_region, &model_volumes, &print_object_regions, &zs_complex, &layer_ranges_regions_to_slices, clip_multipart_objects, &throw_on_cancel_callback] + (const tbb::blocked_range &range) { + float z = zs_complex[*range.begin()]; + it_layer_range = lower_bound_by_predicate(print_object_regions.layer_ranges.begin(), print_object_regions.layer_ranges.end(), + [z](const PrintObjectRegions::LayerRangeRegions &lr){ lr.layer_height_range.first < z; }); + assert(it_layer_range != print_object_regions.layer_ranges.end() && it_layer_range->layer_height_range.first >= z && z < it_layer_range->layer_height_range.second); + // Per volume_regions slices at this Z height. + struct RegionSlice { + ExPolygons expolygons; + // Identifier of this region in PrintObjectRegions::all_regions + int region_id; + ObjectID volume_id; + bool empty() const { return region_id < 0 || expolygons.empty(); } + bool operator<(const RegionSlice &rhs) { + bool this_empty = this->empty(); + return ! this->empty() && (rhs.empty() || ((this->region_id < rhs.region_id) || (this->region_id == rhs.region_id && volume_id < volume_id))); + } + }; + std::vector temp_slices; + for (size_t idx_z = range.begin(); idx_z < range.end(); ++ idx_z) { + for (; it_layer_range->layer_height_range.first < z; ++ it_layer_range) + assert(it_layer_range != print_object_regions.layer_ranges.end()); + assert(it_layer_range != print_object_regions.layer_ranges.end() && it_layer_range->layer_height_range.first >= z && z < it_layer_range->layer_height_range.second); + const PrintObjectRegions::LayerRangeRegions &layer_range = *it_layer_range; + { + SliceEntry &layer_range_regions_to_slices = layer_ranges_regions_to_slices[it_layer_range - print_object_regions.layer_ranges.begin()]; + // Per volume_regions slices at thiz Z height. + temp_slices.clear(); + temp_slices.reserve(layer_range.volume_regions.size()); + for (VolumeSlices *slices : layer_range_regions_to_slices.volume_slices) { + const PrintRegion *region = layer_range.volume_regions[i].region; + temp_slices.push_back({ std::move(slices->slices[idx_z]), region ? region->print_object_region_id() : -1, slices->volume_id }); + } + } + for (int idx_region = 0; idx_region < int(layer_range.volume_regions.size()); ++ idx_region) + if (! temp_slices[idx_region].empty()) { + const PrintObjectRegions::VolumeRegion ®ion = layer_range.volume_regions[idx_region]; + if (region.model_volume->is_modifier()) { + assert(region.parent > -1); + bool next_region_same_modifier = idx_region + 1 < temp_slices.size() && layer_range.volume_regions[idx_region + 1]->model_volume == region.model_volume; + if (next_region_same_modifier) + temp_slices[idx_region + 1] = std::move(temp_slices[idx_region]); + ExPolygons &parent_slice = temp_slices[region.parent]; + ExPolygons &this_slice = temp_slices[idx_region]; + if (parent_slice.empty()) + this_slice.clear(); + else { + ExPolygons &source_slice = temp_slices[idx_region + int(next_region_same_modifier)]; + this_slice = intersection_ex(parent_slice, source_slice); + } + } else if ((region.model_volume->is_model_part() && clip_multipart_objects) || region.model_volume->is_negative_volume()) { + // Clip every non-zero region preceding it. + for (int idx_region2 = 0; idx_region2 < idx_region; ++ idx_region2) + if (! temp_slices[idx_region2].empty()) { + if (const PrintObjectRegions::VolumeRegion ®ion2 = layer_range.volume_regions[idx_region]; + ! region2.model_volume->is_negative_volume() && overlap_in_xy(*region.bbox, *region2.bbox)) + temp_slices[idx_region] = diff_ex(temp_slices[idx_region], temp_slices[idx_region2]); + } + } + } + } + // Sort by region_id, push empty slices to the end. + std::sort(temp_slices.begin(), temp_slices.end()); + // Remove the empty slices. + temp_slices.erase(temp_slices.begin(), std::find_first(temp_slices.begin(), temp_slices.end(), [](const auto &slice){ return slice.empty(); })); + // Merge slices and store them to the output. + for (int i = 0; i < temp_slices.size();) { + // Find a range of temp_slices with the same region_id. + int j = i; + bool merged = false; + ExPolygons &expolygons = temp_slices[i].expolygons; + for (++ j; + j < temp_slices.size() && + temp_slices[i].region_id == temp_slices[j].region_id && + (clip_multipart_objects || temp_slices[i].volume_id == temp_slices[j].volume_id); + ++ j) + if (ExPolygons &expolygons2 = temp_slices[j].expolygons; ! expolygons2.empty()) + if (expolygons.empty()) + expolygons = std::move(expolygons2); + else { + append(expolygons, expolygons2); + merged = true; + } + if (merged) + expolygons = offset_ex(offset_ex(expolygons, float(scale_(EPSILON))), -float(scale_(EPSILON))); + slices_by_region[temp_slices[i].region_id][z_idx] = std::move(expolygons); + i = j; + } + + }); + } + } +} + +// Z ranges are not applicable to modifier meshes, therefore a single volume will be found in volume_w_zrange at most once. +std::vector PrintObject::slice_modifiers(size_t region_id, const std::vector &slice_zs) const +{ + std::vector out; + if (region_id < m_region_volumes.size()) + { + std::vector> volume_ranges; + const PrintRegionVolumes &volumes_and_ranges = m_region_volumes[region_id]; + volume_ranges.reserve(volumes_and_ranges.volumes.size()); + for (size_t i = 0; i < volumes_and_ranges.volumes.size(); ) { + int volume_id = volumes_and_ranges.volumes[i].volume_idx; + const ModelVolume *model_volume = this->model_object()->volumes[volume_id]; + if (model_volume->is_modifier()) { + std::vector ranges; + ranges.emplace_back(volumes_and_ranges.volumes[i].layer_height_range); + size_t j = i + 1; + for (; j < volumes_and_ranges.volumes.size() && volume_id == volumes_and_ranges.volumes[j].volume_idx; ++ j) { + if (! ranges.empty() && std::abs(ranges.back().second - volumes_and_ranges.volumes[j].layer_height_range.first) < EPSILON) + ranges.back().second = volumes_and_ranges.volumes[j].layer_height_range.second; + else + ranges.emplace_back(volumes_and_ranges.volumes[j].layer_height_range); + } + volume_ranges.emplace_back(std::move(ranges)); + i = j; + } else + ++ i; + } + + if (! volume_ranges.empty()) + { + bool equal_ranges = true; + for (size_t i = 1; i < volume_ranges.size(); ++ i) { + assert(! volume_ranges[i].empty()); + if (volume_ranges.front() != volume_ranges[i]) { + equal_ranges = false; + break; + } + } + + if (equal_ranges && volume_ranges.front().size() == 1 && volume_ranges.front().front() == t_layer_height_range(0, DBL_MAX)) { + // No modifier in this region was split to layer spans. + std::vector volumes; + for (const PrintRegionVolumes::VolumeWithZRange &volume_w_zrange : m_region_volumes[region_id].volumes) { + const ModelVolume *volume = this->model_object()->volumes[volume_w_zrange.volume_idx]; + if (volume->is_modifier()) + volumes.emplace_back(volume); + } + out = this->slice_volumes(slice_zs, SlicingMode::Regular, volumes); + } else { + // Some modifier in this region was split to layer spans. + std::vector merge; + for (size_t region_id = 0; region_id < m_region_volumes.size(); ++ region_id) { + const PrintRegionVolumes &volumes_and_ranges = m_region_volumes[region_id]; + for (size_t i = 0; i < volumes_and_ranges.volumes.size(); ) { + int volume_id = volumes_and_ranges.volumes[i].volume_idx; + const ModelVolume *model_volume = this->model_object()->volumes[volume_id]; + if (model_volume->is_modifier()) { + BOOST_LOG_TRIVIAL(debug) << "Slicing modifiers - volume " << volume_id; + // Find the ranges of this volume. Ranges in volumes_and_ranges must not overlap for a single volume. + std::vector ranges; + ranges.emplace_back(volumes_and_ranges.volumes[i].layer_height_range); + size_t j = i + 1; + for (; j < volumes_and_ranges.volumes.size() && volume_id == volumes_and_ranges.volumes[j].volume_idx; ++ j) + ranges.emplace_back(volumes_and_ranges.volumes[j].layer_height_range); + // slicing in parallel + std::vector this_slices = this->slice_volume(slice_zs, ranges, SlicingMode::Regular, *model_volume); + // Variable this_slices could be empty if no value of slice_zs is within any of the ranges of this volume. + if (out.empty()) { + out = std::move(this_slices); + merge.assign(out.size(), false); + } else if (!this_slices.empty()) { + assert(out.size() == this_slices.size()); + for (size_t i = 0; i < out.size(); ++ i) + if (! this_slices[i].empty()) { + if (! out[i].empty()) { + append(out[i], this_slices[i]); + merge[i] = true; + } else + out[i] = std::move(this_slices[i]); + } + } + i = j; + } else + ++ i; + } + } + for (size_t i = 0; i < merge.size(); ++ i) + if (merge[i]) + out[i] = union_ex(out[i]); + } + } + } + + return out; +} + +std::string PrintObject::_fix_slicing_errors() +{ + // Collect layers with slicing errors. + // These layers will be fixed in parallel. + std::vector buggy_layers; + buggy_layers.reserve(m_layers.size()); + for (size_t idx_layer = 0; idx_layer < m_layers.size(); ++ idx_layer) + if (m_layers[idx_layer]->slicing_errors) + buggy_layers.push_back(idx_layer); + + BOOST_LOG_TRIVIAL(debug) << "Slicing objects - fixing slicing errors in parallel - begin"; + tbb::parallel_for( + tbb::blocked_range(0, buggy_layers.size()), + [this, &buggy_layers](const tbb::blocked_range& range) { + for (size_t buggy_layer_idx = range.begin(); buggy_layer_idx < range.end(); ++ buggy_layer_idx) { + m_print->throw_if_canceled(); + size_t idx_layer = buggy_layers[buggy_layer_idx]; + Layer *layer = m_layers[idx_layer]; + assert(layer->slicing_errors); + // Try to repair the layer surfaces by merging all contours and all holes from neighbor layers. + // BOOST_LOG_TRIVIAL(trace) << "Attempting to repair layer" << idx_layer; + for (size_t region_id = 0; region_id < layer->m_regions.size(); ++ region_id) { + LayerRegion *layerm = layer->m_regions[region_id]; + // Find the first valid layer below / above the current layer. + const Surfaces *upper_surfaces = nullptr; + const Surfaces *lower_surfaces = nullptr; + for (size_t j = idx_layer + 1; j < m_layers.size(); ++ j) + if (! m_layers[j]->slicing_errors) { + upper_surfaces = &m_layers[j]->regions()[region_id]->slices.surfaces; + break; + } + for (int j = int(idx_layer) - 1; j >= 0; -- j) + if (! m_layers[j]->slicing_errors) { + lower_surfaces = &m_layers[j]->regions()[region_id]->slices.surfaces; + break; + } + // Collect outer contours and holes from the valid layers above & below. + Polygons outer; + outer.reserve( + ((upper_surfaces == nullptr) ? 0 : upper_surfaces->size()) + + ((lower_surfaces == nullptr) ? 0 : lower_surfaces->size())); + size_t num_holes = 0; + if (upper_surfaces) + for (const auto &surface : *upper_surfaces) { + outer.push_back(surface.expolygon.contour); + num_holes += surface.expolygon.holes.size(); + } + if (lower_surfaces) + for (const auto &surface : *lower_surfaces) { + outer.push_back(surface.expolygon.contour); + num_holes += surface.expolygon.holes.size(); + } + Polygons holes; + holes.reserve(num_holes); + if (upper_surfaces) + for (const auto &surface : *upper_surfaces) + polygons_append(holes, surface.expolygon.holes); + if (lower_surfaces) + for (const auto &surface : *lower_surfaces) + polygons_append(holes, surface.expolygon.holes); + layerm->slices.set(diff_ex(union_(outer), holes), stInternal); + } + // Update layer slices after repairing the single regions. + layer->make_slices(); + } + }); + m_print->throw_if_canceled(); + BOOST_LOG_TRIVIAL(debug) << "Slicing objects - fixing slicing errors in parallel - end"; + + // remove empty layers from bottom + while (! m_layers.empty() && (m_layers.front()->lslices.empty() || m_layers.front()->empty())) { + delete m_layers.front(); + m_layers.erase(m_layers.begin()); + m_layers.front()->lower_layer = nullptr; + for (size_t i = 0; i < m_layers.size(); ++ i) + m_layers[i]->set_id(m_layers[i]->id() - 1); + } + + return buggy_layers.empty() ? "" : + "The model has overlapping or self-intersecting facets. I tried to repair it, " + "however you might want to check the results or repair the input file and retry.\n"; +} + +// Simplify the sliced model, if "resolution" configuration parameter > 0. +// The simplification is problematic, because it simplifies the slices independent from each other, +// which makes the simplified discretization visible on the object surface. +void PrintObject::simplify_slices(double distance) +{ + BOOST_LOG_TRIVIAL(debug) << "Slicing objects - siplifying slices in parallel - begin"; + tbb::parallel_for( + tbb::blocked_range(0, m_layers.size()), + [this, distance](const tbb::blocked_range& range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { + m_print->throw_if_canceled(); + Layer *layer = m_layers[layer_idx]; + for (size_t region_idx = 0; region_idx < layer->m_regions.size(); ++ region_idx) + layer->m_regions[region_idx]->slices.simplify(distance); + { + ExPolygons simplified; + for (const ExPolygon &expoly : layer->lslices) + expoly.simplify(distance, &simplified); + layer->lslices = std::move(simplified); + } + } + }); + BOOST_LOG_TRIVIAL(debug) << "Slicing objects - siplifying slices in parallel - end"; +} + +// Called by make_perimeters() +// 1) Decides Z positions of the layers, +// 2) Initializes layers and their regions +// 3) Slices the object meshes +// 4) Slices the modifier meshes and reclassifies the slices of the object meshes by the slices of the modifier meshes +// 5) Applies size compensation (offsets the slices in XY plane) +// 6) Replaces bad slices by the slices reconstructed from the upper/lower layer +// Resulting expolygons of layer regions are marked as Internal. +void PrintObject::slice() +{ + if (! this->set_started(posSlice)) + return; + m_print->set_status(10, L("Processing triangulated mesh")); + std::vector layer_height_profile; + this->update_layer_height_profile(*this->model_object(), m_slicing_params, layer_height_profile); + m_print->throw_if_canceled(); + m_typed_slices = false; + this->clear_layers(); + m_layers = new_layers(this, generate_object_layers(m_slicing_params, layer_height_profile), m_slicing_params.raft_layers()); + this->_slice(); + m_print->throw_if_canceled(); + // Fix the model. + //FIXME is this the right place to do? It is done repeateadly at the UI and now here at the backend. + std::string warning = this->_fix_slicing_errors(); + m_print->throw_if_canceled(); + if (! warning.empty()) + BOOST_LOG_TRIVIAL(info) << warning; + // Update bounding boxes, back up raw slices of complex models. + tbb::parallel_for( + tbb::blocked_range(0, m_layers.size()), + [this](const tbb::blocked_range& range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { + m_print->throw_if_canceled(); + Layer &layer = *m_layers[layer_idx]; + layer.lslices_bboxes.clear(); + layer.lslices_bboxes.reserve(layer.lslices.size()); + for (const ExPolygon &expoly : layer.lslices) + layer.lslices_bboxes.emplace_back(get_extents(expoly)); + layer.backup_untyped_slices(); + } + }); + if (m_layers.empty()) + throw Slic3r::SlicingError("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n"); + this->set_done(posSlice); +} + +// 1) Decides Z positions of the layers, +// 2) Initializes layers and their regions +// 3) Slices the object meshes +// 4) Slices the modifier meshes and reclassifies the slices of the object meshes by the slices of the modifier meshes +// 5) Applies size compensation (offsets the slices in XY plane) +// 6) Replaces bad slices by the slices reconstructed from the upper/lower layer +// Resulting expolygons of layer regions are marked as Internal. +// +// this should be idempotent +void PrintObject::slice_volumes() +{ + BOOST_LOG_TRIVIAL(info) << "Slicing volumes..." << log_memory_info(); + + bool spiral_vase = this->print()->config().spiral_vase; + auto throw_on_cancel_callback = TriangleMeshSlicer::throw_on_cancel_callback_type([print](){print->throw_if_canceled();}); + + std::vector slice_zs = zs_from_layers(m_layers); + + std::vector> region_slices = slices_to_regions(this->model_object()->volumes, m_shared_regions->layer_ranges, slice_zs, + slice_volumes( + this->print()->config(), this->config(), + this->model_object()->volumes, m_shared_regions->layer_ranges, m_center_offset, slice_zs, SlicingMode::Regular, spiral_vase, throw_on_cancel_callback), + m_config.clip_multipart_objects, + throw_on_cancel_callback); + + for (size_t region_id = 0; region_id < region_slices.size(); ++ region_id) { + std::vector &by_layer = region_slices[region_id]; + for (size_t layer_id = 0; layer_id < by_layer.size(); ++ layer_id) + m_layers[layer_id]->regions()[region_id]->slices.append(std::move(by_layer[layer_id]), stInternal); + } + region_slices.clear(); + + // Second clip the volumes in the order they are presented at the user interface. + BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - parallel clipping - start"; + tbb::parallel_for( + tbb::blocked_range(0, slice_zs.size()), + [this, &sliced_volumes, num_modifiers](const tbb::blocked_range& range) { + float delta = float(scale_(m_config.xy_size_compensation.value)); + // Only upscale together with clipping if there are no modifiers, as the modifiers shall be applied before upscaling + // (upscaling may grow the object outside of the modifier mesh). + bool upscale = delta > 0 && num_modifiers == 0; + for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { + m_print->throw_if_canceled(); + // Trim volumes in a single layer, one by the other, possibly apply upscaling. + { + Polygons processed; + for (SlicedVolume &sliced_volume : sliced_volumes) + if (! sliced_volume.expolygons_by_layer.empty()) { + ExPolygons slices = std::move(sliced_volume.expolygons_by_layer[layer_id]); + if (upscale) + slices = offset_ex(std::move(slices), delta); + if (! processed.empty()) + // Trim by the slices of already processed regions. + slices = diff_ex(slices, processed); + if (size_t(&sliced_volume - &sliced_volumes.front()) + 1 < sliced_volumes.size()) + // Collect the already processed regions to trim the to be processed regions. + polygons_append(processed, slices); + sliced_volume.expolygons_by_layer[layer_id] = std::move(slices); + } + } + // Collect and union volumes of a single region. + for (int region_id = 0; region_id < int(m_region_volumes.size()); ++ region_id) { + ExPolygons expolygons; + size_t num_volumes = 0; + for (SlicedVolume &sliced_volume : sliced_volumes) + if (sliced_volume.region_id == region_id && ! sliced_volume.expolygons_by_layer.empty() && ! sliced_volume.expolygons_by_layer[layer_id].empty()) { + ++ num_volumes; + append(expolygons, std::move(sliced_volume.expolygons_by_layer[layer_id])); + } + if (num_volumes > 1) + // Merge the islands using a positive / negative offset. + expolygons = offset_ex(offset_ex(expolygons, float(scale_(EPSILON))), -float(scale_(EPSILON))); + m_layers[layer_id]->regions()[region_id]->slices.append(std::move(expolygons), stInternal); + } + } + }); + BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - parallel clipping - end"; + clipped = true; + upscaled = m_config.xy_size_compensation.value > 0 && num_modifiers == 0; + } + + BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - removing top empty layers"; + while (! m_layers.empty()) { + const Layer *layer = m_layers.back(); + if (! layer->empty()) + break; + delete layer; + m_layers.pop_back(); + } + if (! m_layers.empty()) + m_layers.back()->upper_layer = nullptr; + m_print->throw_if_canceled(); + + BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - make_slices in parallel - begin"; + { + // Compensation value, scaled. + const float xy_compensation_scaled = float(scale_(m_config.xy_size_compensation.value)); + const float elephant_foot_compensation_scaled = (m_config.raft_layers == 0) ? + // Only enable Elephant foot compensation if printing directly on the print bed. + float(scale_(m_config.elefant_foot_compensation.value)) : + 0.f; + // Uncompensated slices for the first layer in case the Elephant foot compensation is applied. + ExPolygons lslices_1st_layer; + tbb::parallel_for( + tbb::blocked_range(0, m_layers.size()), + [this, upscaled, clipped, xy_compensation_scaled, elephant_foot_compensation_scaled, &lslices_1st_layer] + (const tbb::blocked_range& range) { + for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { + m_print->throw_if_canceled(); + Layer *layer = m_layers[layer_id]; + // Apply size compensation and perform clipping of multi-part objects. + float elfoot = (layer_id == 0) ? elephant_foot_compensation_scaled : 0.f; + if (layer->m_regions.size() == 1) { + assert(! upscaled); + assert(! clipped); + // Optimized version for a single region layer. + // Single region, growing or shrinking. + LayerRegion *layerm = layer->m_regions.front(); + if (elfoot > 0) { + // Apply the elephant foot compensation and store the 1st layer slices without the Elephant foot compensation applied. + lslices_1st_layer = to_expolygons(std::move(layerm->slices.surfaces)); + float delta = xy_compensation_scaled; + if (delta > elfoot) { + delta -= elfoot; + elfoot = 0.f; + } else if (delta > 0) + elfoot -= delta; + layerm->slices.set( + union_ex( + Slic3r::elephant_foot_compensation( + (delta == 0.f) ? lslices_1st_layer : offset_ex(lslices_1st_layer, delta), + layerm->flow(frExternalPerimeter), unscale(elfoot))), + stInternal); + if (xy_compensation_scaled != 0.f) + lslices_1st_layer = offset_ex(std::move(lslices_1st_layer), xy_compensation_scaled); + } else if (xy_compensation_scaled != 0.f) { + // Apply the XY compensation. + layerm->slices.set( + offset_ex(to_expolygons(std::move(layerm->slices.surfaces)), xy_compensation_scaled), + stInternal); + } + } else { + bool upscale = ! upscaled && xy_compensation_scaled > 0.f; + bool clip = ! clipped && m_config.clip_multipart_objects.value; + if (upscale || clip) { + // Multiple regions, growing or just clipping one region by the other. + // When clipping the regions, priority is given to the first regions. + Polygons processed; + for (size_t region_id = 0; region_id < layer->m_regions.size(); ++ region_id) { + LayerRegion *layerm = layer->m_regions[region_id]; + ExPolygons slices = to_expolygons(std::move(layerm->slices.surfaces)); + if (upscale) + slices = offset_ex(std::move(slices), xy_compensation_scaled); + if (region_id > 0 && clip) + // Trim by the slices of already processed regions. + slices = diff_ex(slices, processed); + if (clip && (region_id + 1 < layer->m_regions.size())) + // Collect the already processed regions to trim the to be processed regions. + polygons_append(processed, slices); + layerm->slices.set(std::move(slices), stInternal); + } + } + if (xy_compensation_scaled < 0.f || elfoot > 0.f) { + // Apply the negative XY compensation. + Polygons trimming; + static const float eps = float(scale_(m_config.slice_closing_radius.value) * 1.5); + if (elfoot > 0.f) { + lslices_1st_layer = offset_ex(layer->merged(eps), std::min(xy_compensation_scaled, 0.f) - eps); + trimming = to_polygons(Slic3r::elephant_foot_compensation(lslices_1st_layer, + layer->m_regions.front()->flow(frExternalPerimeter), unscale(elfoot))); + } else + trimming = offset(layer->merged(float(SCALED_EPSILON)), xy_compensation_scaled - float(SCALED_EPSILON)); + for (size_t region_id = 0; region_id < layer->m_regions.size(); ++ region_id) + layer->m_regions[region_id]->trim_surfaces(trimming); + } + } + // Merge all regions' slices to get islands, chain them by a shortest path. + layer->make_slices(); + } + }); + if (elephant_foot_compensation_scaled > 0.f && ! m_layers.empty()) { + // The Elephant foot has been compensated, therefore the 1st layer's lslices are shrank with the Elephant foot compensation value. + // Store the uncompensated value there. + assert(m_layers.front()->id() == 0); + m_layers.front()->lslices = std::move(lslices_1st_layer); + } + } + + m_print->throw_if_canceled(); + BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - make_slices in parallel - end"; +} + +std::vector PrintObject::slice_support_volumes(const ModelVolumeType model_volume_type) const +{ + size_t it_volume = this->model_object()->volumes.begin(); + size_t it_volume_end = this->model_object()->volumes.end(); + for (; it_volume->type() != model_volume_type && it_volume != it_volume_end; ++ it_volume) ; + std::vector slices; + if (it_volume != it_volume_end) { + // Found at least a single support volume of model_volume_type. + std::vector zs = zs_from_layers(this->layers()); + std::vector merge_layers; + bool merge = false; + const Print *print = this->print(); + auto throw_on_cancel_callback = TriangleMeshSlicer::throw_on_cancel_callback_type([print](){print->throw_if_canceled();}); + for (; it_volume != it_volume_end; ++ it_volume; it_volume != it_volume_end) + if (it_volume->type() == model_volume_type) { + std::vector slices2 = slice_volume(*(*it_volume), zs, SlicingMode::Regular, throw_on_cancel_callback); + if (slices.empty()) + slices = std::move(slices2); + else if (! slices2.empty()) { + if (merge_layers.empty()) + merge_layers.assign(zs.size(), false); + for (size_t i = 0; i < zs.size(); ++ i) { + if (slices[i].empty()) + slices[i] = std::move(slices2[i]); + else if (! slices2[i].empty()) { + append(slices[i], std::move(slices2[i])); + merge_layers[i] = true; + merge = true; + } + } + } + } + if (merge) { + std::vector to_merge; + to_merge.reserve(zs); + for (size_t i = 0; i < zs.size(); ++ i) + if (merge_layers[i]) + to_merge.emplace_back(&slices[i]); + tbb::parallel_for( + tbb::blocked_range(0, to_merge.size()), + [&to_merge](const tbb::blocked_range &range) { + for (size_t i = range.begin(); i < range.end(); ++ i) + to_merge[i] = union_ex(to_merge[i]); + }); + } + } + return slices; +} + +} // namespace Slic3r