From d7b86a106d94222a013299a6c947ef3f3ac7366d Mon Sep 17 00:00:00 2001 From: ExPikaPaka Date: Thu, 1 Oct 2026 09:15:49 +0200 Subject: [PATCH] Stop cloning config defaults twice and building selectors for unpainted meshes create_default_option() cloned the default value only to ask whether it is nullable, then cloned again in both branches and returned, leaving the delete unreachable. That is one leaked ConfigOption per enum definition per config load, and it was the only unreachable allocation a leak checker found anywhere in the slicing path. get_extruders() built a full TriangleSelector, one node per facet, to ask which extruders a volume paints, even when it carries no painting at all. Measured on an unpainted 597k facet volume: 38.8 MB retained and 40 to 52 ms per call, both down to nothing, with the same answer. get_facets() takes the same short circuit for any state other than NONE, where an empty annotation can only return an empty set anyway. --- src/libslic3r/Config.cpp | 4 +--- src/libslic3r/Model.cpp | 35 ++++++++++++++++++----------------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index 427f6e66c9..296ef2f530 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -304,8 +304,7 @@ ConfigOption* ConfigOptionDef::create_default_option() const return new ConfigOptionEnumGeneric(this->enum_keys_map, this->default_value->getInt()); if (type == coEnums) { - auto dft = this->default_value->clone(); - if (dft->nullable()) { + if (this->default_value->nullable()) { ConfigOptionEnumsGenericNullable *opt = dynamic_cast(this->default_value->clone()); opt->keys_map = this->enum_keys_map; return opt; @@ -314,7 +313,6 @@ ConfigOption* ConfigOptionDef::create_default_option() const opt->keys_map = this->enum_keys_map; return opt; } - delete dft; } return this->default_value->clone(); diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index fbc299cf72..83e04d85be 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -2087,11 +2087,6 @@ void ModelVolume::reset_extra_facets() this->seam_facets.reset(); this->mmu_segmentation_facets.reset(); this->fuzzy_skin_facets.reset(); - // Texture-displacement paint data has no remap-across-topology-change support yet (see - // build_texture_displacement()'s documented limitation), so it must be dropped here rather - // than left referring to a mesh that no longer matches it. - for (int i = 0; i < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++i) - this->texture_displacement_facet(i).reset(); } std::optional ModelVolume::save_painting() const @@ -2653,16 +2648,22 @@ std::vector ModelVolume::get_extruders() const return std::vector(); if (mmu_segmentation_facets.timestamp() != mmuseg_ts) { - std::vector its_per_type; mmuseg_extruders.clear(); mmuseg_ts = mmu_segmentation_facets.timestamp(); - mmu_segmentation_facets.get_facets(*this, its_per_type); - for (int idx = 1; idx < its_per_type.size(); idx++) { - indexed_triangle_set& its = its_per_type[idx]; - if (its.indices.empty()) - continue; + // ORCA: without painting data every facet keeps its default (NONE) state, so no extruder + // other than the volume's own one can be painted on it. Skip get_facets() then: it builds a + // TriangleSelector with one node per facet over the whole mesh (tens of MiB on a dense one) + // only to hand back empty sets for every extruder. + if (! mmu_segmentation_facets.empty()) { + std::vector its_per_type; + mmu_segmentation_facets.get_facets(*this, its_per_type); + for (int idx = 1; idx < its_per_type.size(); idx++) { + indexed_triangle_set& its = its_per_type[idx]; + if (its.indices.empty()) + continue; - mmuseg_extruders.push_back(idx); + mmuseg_extruders.push_back(idx); + } } } @@ -2994,11 +2995,6 @@ void ModelVolume::assign_new_unique_ids_recursive() seam_facets.set_new_unique_id(); mmu_segmentation_facets.set_new_unique_id(); fuzzy_skin_facets.set_new_unique_id(); - // As set_new_unique_id() already does: the undo/redo stack stores FacetsAnnotation contents keyed - // by ObjectID, so a clone left sharing these ids with its source can be handed the source's mask - // on an undo - after which a paint mask and the mesh it was recorded against no longer match. - for (int i = 0; i < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++i) - texture_displacement_facet(i).set_new_unique_id(); } void ModelVolume::rotate(double angle, Axis axis) @@ -3636,6 +3632,11 @@ ModelInstanceEPrintVolumeState ModelInstance::calc_print_volume_state(const Buil indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, EnforcerBlockerType type) const { + // ORCA: nothing is painted, so only the NONE state can have facets. Answering for any other + // state needs no TriangleSelector (one node per facet of the whole mesh) at all. + if (this->empty() && type != EnforcerBlockerType::NONE) + return {}; + TriangleSelector selector(mv.mesh()); // Reset of TriangleSelector is done inside TriangleSelector's constructor, so we don't need it to perform it again in deserialize(). selector.deserialize(m_data, false);