From 07173a8de41a4974d0e67052a4b5b1b0068b6923 Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Tue, 28 Apr 2026 22:36:30 +0800 Subject: [PATCH 001/117] Remove unused facets --- src/libslic3r/Model.cpp | 6 +----- src/libslic3r/Model.hpp | 3 --- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index e3b2ada837..ef5ef6dbe6 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -2714,11 +2714,7 @@ size_t ModelVolume::split(unsigned int max_extruders) this->source = ModelVolume::Source(); // BBS: reset facet annotations - this->mmu_segmentation_facets.reset(); - this->exterior_facets.reset(); - this->supported_facets.reset(); - this->seam_facets.reset(); - this->fuzzy_skin_facets.reset(); + this->reset_extra_facets(); } else this->object->volumes.insert(this->object->volumes.begin() + (++ivolume), new ModelVolume(object, *this, std::move(mesh))); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 5974ca4cd6..3fd66f5f69 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -881,9 +881,6 @@ public: mutable std::vector mmuseg_extruders; mutable Timestamp mmuseg_ts; - // List of exterior faces - FacetsAnnotation exterior_facets; - // Is set only when volume is Embossed Text type // Contain information how to re-create volume std::optional text_configuration; From ded2e72dcc47bc7cb38f1a50ac1605e1179f77db Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Tue, 28 Apr 2026 22:47:41 +0800 Subject: [PATCH 002/117] Refine code --- src/libslic3r/CutUtils.cpp | 8 ++++---- src/libslic3r/TriangleMeshSlicer.cpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/CutUtils.cpp b/src/libslic3r/CutUtils.cpp index 8611ec302d..eff267588c 100644 --- a/src/libslic3r/CutUtils.cpp +++ b/src/libslic3r/CutUtils.cpp @@ -566,17 +566,17 @@ const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Tran }; // cut by upper plane - - const Transform3d cut_matrix_upper = translation_transform(rotation_m * (groove_half_depth * Vec3d::UnitZ())) * m_cut_matrix; { + const Transform3d cut_matrix_upper = translation_transform(rotation_m * (groove_half_depth * Vec3d::UnitZ())) * m_cut_matrix; + cut(tmp_object, cut_matrix_upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); } // cut by lower plane - - const Transform3d cut_matrix_lower = translation_transform(rotation_m * (-groove_half_depth * Vec3d::UnitZ())) * m_cut_matrix; { + const Transform3d cut_matrix_lower = translation_transform(rotation_m * (-groove_half_depth * Vec3d::UnitZ())) * m_cut_matrix; + cut(tmp_object, cut_matrix_lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); } diff --git a/src/libslic3r/TriangleMeshSlicer.cpp b/src/libslic3r/TriangleMeshSlicer.cpp index 150c456b8b..2c1c0da23f 100644 --- a/src/libslic3r/TriangleMeshSlicer.cpp +++ b/src/libslic3r/TriangleMeshSlicer.cpp @@ -2348,7 +2348,7 @@ void cut_mesh(const indexed_triangle_set& mesh, float z, indexed_triangle_set* u IntersectionLines upper_lines, lower_lines; std::vector upper_slice_vertices, lower_slice_vertices; std::vector facets_edge_ids = its_face_edge_ids(mesh); - std::map section_vertices_map; + std::map section_vertices_map; // vertices on the cut plane for (int facet_idx = 0; facet_idx < int(mesh.indices.size()); ++ facet_idx) { const stl_triangle_vertex_indices &facet = mesh.indices[facet_idx]; From 24458ba5a20048394175ba9adb74f5582cfa54cf Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Mon, 4 May 2026 20:32:39 +0800 Subject: [PATCH 003/117] Remapping the paintings after cut --- src/libslic3r/CutUtils.cpp | 68 ++++++- src/libslic3r/Model.hpp | 1 + src/libslic3r/TriangleSelector.cpp | 292 +++++++++++++++++++++++++++-- src/libslic3r/TriangleSelector.hpp | 12 +- 4 files changed, 349 insertions(+), 24 deletions(-) diff --git a/src/libslic3r/CutUtils.cpp b/src/libslic3r/CutUtils.cpp index eff267588c..7979dc4095 100644 --- a/src/libslic3r/CutUtils.cpp +++ b/src/libslic3r/CutUtils.cpp @@ -8,11 +8,39 @@ #include "ObjectID.hpp" #include +#include namespace Slic3r { using namespace Geometry; +// Saved painting data for remapping after mesh cutting. +struct SavedPainting { + indexed_triangle_set its; // Original mesh transformed to cut space + TriangleSelector::TriangleSplittingData supported; + TriangleSelector::TriangleSplittingData seam; + TriangleSelector::TriangleSplittingData mmu; + TriangleSelector::TriangleSplittingData fuzzy; +}; + +// Remap painting data from saved source to a cut result mesh, and set on a volume. +static void remap_and_set_painting(ModelVolume* vol, const indexed_triangle_set& cut_its, + const SavedPainting& saved) +{ + auto remap_one = [&](const TriangleSelector::TriangleSplittingData& src_data, + FacetsAnnotation& target_facets) { + if (src_data.bitstream.empty()) + return; + auto result = TriangleSelector::remap_painting(saved.its, src_data, cut_its); + if (!result.bitstream.empty()) + target_facets.set_data(result); + }; + remap_one(saved.supported, vol->supported_facets); + remap_one(saved.seam, vol->seam_facets); + remap_one(saved.mmu, vol->mmu_segmentation_facets); + remap_one(saved.fuzzy, vol->fuzzy_skin_facets); +} + static void apply_tolerance(ModelVolume* vol) { ModelVolume::CutInfo& cut_info = vol->cut_info; @@ -179,7 +207,8 @@ static void process_modifier_cut(ModelVolume* volume, const Transform3d& instanc } static void process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, - ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower) + ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, + const SavedPainting* saved_painting = nullptr) { // Perform cut TriangleMesh upper_mesh, lower_mesh; @@ -189,18 +218,28 @@ static void process_solid_part_cut(ModelVolume* volume, const Transform3d& insta if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) { add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A"); + if (saved_painting && !upper_mesh.empty()) + remap_and_set_painting(upper->volumes.back(), upper_mesh.its, *saved_painting); if (!lower_mesh.empty()) { add_cut_volume(lower_mesh, upper, volume, cut_matrix, "_B"); + if (saved_painting) + remap_and_set_painting(upper->volumes.back(), lower_mesh.its, *saved_painting); upper->volumes.back()->cut_info.is_from_upper = false; } return; } - if (attributes.has(ModelObjectCutAttribute::KeepUpper)) + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { add_cut_volume(upper_mesh, upper, volume, cut_matrix); + if (saved_painting && !upper_mesh.empty()) + remap_and_set_painting(upper->volumes.back(), upper_mesh.its, *saved_painting); + } - if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty()) + if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty()) { add_cut_volume(lower_mesh, lower, volume, cut_matrix); + if (saved_painting) + remap_and_set_painting(lower->volumes.back(), lower_mesh.its, *saved_painting); + } } static void reset_instance_transformation(ModelObject* object, size_t src_instance_idx, @@ -321,6 +360,26 @@ const ModelObjectPtrs& Cut::perform_with_plane() const Transform3d inverse_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1. * cut_transformation.get_offset()); for (ModelVolume* volume : mo->volumes) { + // Save painting data before reset_extra_facets() discards it. + // Only for model parts that will be cut (not modifiers/connectors). + std::optional saved_painting; + if (volume->is_model_part() && !volume->mesh().empty()) { + SavedPainting sp; + // Get mesh in cut space (same transform as process_volume_cut applies) + TriangleMesh mesh(volume->mesh()); + const auto volume_matrix = volume->get_matrix(); + mesh.transform(inverse_cut_matrix * instance_matrix * volume_matrix, true); + mesh.transform(m_cut_matrix); + sp.its = std::move(mesh.its); + sp.supported = volume->supported_facets.get_data(); + sp.seam = volume->seam_facets.get_data(); + sp.mmu = volume->mmu_segmentation_facets.get_data(); + sp.fuzzy = volume->fuzzy_skin_facets.get_data(); + if (!sp.supported.bitstream.empty() || !sp.seam.bitstream.empty() || + !sp.mmu.bitstream.empty() || !sp.fuzzy.bitstream.empty()) + saved_painting = std::move(sp); + } + volume->reset_extra_facets(); if (!volume->is_model_part()) { @@ -330,7 +389,8 @@ const ModelObjectPtrs& Cut::perform_with_plane() process_connector_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower, dowels); } else if (!volume->mesh().empty()) - process_solid_part_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower); + process_solid_part_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower, + saved_painting ? &*saved_painting : nullptr); } // Post-process cut parts diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 3fd66f5f69..302bf47935 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -731,6 +731,7 @@ public: void assign(const FacetsAnnotation &rhs) { if (! this->timestamp_matches(rhs)) { m_data = rhs.m_data; this->copy_timestamp(rhs); } } void assign(FacetsAnnotation &&rhs) { if (! this->timestamp_matches(rhs)) { m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } } const TriangleSelector::TriangleSplittingData &get_data() const noexcept { return m_data; } + void set_data(const TriangleSelector::TriangleSplittingData &data) { m_data = data; this->touch(); } bool set(const TriangleSelector& selector); indexed_triangle_set get_facets(const ModelVolume& mv, EnforcerBlockerType type) const; // BBS diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index a6d19f505c..6368955fce 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -1,9 +1,11 @@ #include "TriangleSelector.hpp" #include "Model.hpp" +#include "AABBTreeIndirect.hpp" #include #include #include +#include #include #ifndef NDEBUG @@ -168,24 +170,25 @@ void TriangleSelector::Triangle::set_division(int sides_to_split, int special_si this->special_side_idx = char(special_side_idx); } +// Real-time collision detection, Ericson, Chapter 3.4 +inline Vec3f barycentric(const Vec3f& pt, const Vec3f& p1, const Vec3f& p2, const Vec3f& p3) +{ + std::array v = {p2 - p1, p3 - p1, pt - p1}; + float d00 = v[0].dot(v[0]); + float d01 = v[0].dot(v[1]); + float d11 = v[1].dot(v[1]); + float d20 = v[2].dot(v[0]); + float d21 = v[2].dot(v[1]); + float denom = d00 * d11 - d01 * d01; + + Vec3f barycentric_cords(1.f, (d11 * d20 - d01 * d21) / denom, (d00 * d21 - d01 * d20) / denom); + barycentric_cords.x() = barycentric_cords.x() - barycentric_cords.y() - barycentric_cords.z(); + return barycentric_cords; +} + inline bool is_point_inside_triangle(const Vec3f &pt, const Vec3f &p1, const Vec3f &p2, const Vec3f &p3) { - // Real-time collision detection, Ericson, Chapter 3.4 - auto barycentric = [&pt, &p1, &p2, &p3]() -> Vec3f { - std::array v = {p2 - p1, p3 - p1, pt - p1}; - float d00 = v[0].dot(v[0]); - float d01 = v[0].dot(v[1]); - float d11 = v[1].dot(v[1]); - float d20 = v[2].dot(v[0]); - float d21 = v[2].dot(v[1]); - float denom = d00 * d11 - d01 * d01; - - Vec3f barycentric_cords(1.f, (d11 * d20 - d01 * d21) / denom, (d00 * d21 - d01 * d20) / denom); - barycentric_cords.x() = barycentric_cords.x() - barycentric_cords.y() - barycentric_cords.z(); - return barycentric_cords; - }; - - Vec3f barycentric_cords = barycentric(); + Vec3f barycentric_cords = barycentric(pt, p1, p2, p3); return std::all_of(begin(barycentric_cords), end(barycentric_cords), [](float cord) { return 0.f <= cord && cord <= 1.0; }); } @@ -935,7 +938,7 @@ bool TriangleSelector::select_triangle_recursive(int facet_idx, const Vec3i32 &n if (triangle_splitting) split_triangle(facet_idx, neighbors); - else if (!m_triangles[facet_idx].is_split()) + if (!m_triangles[facet_idx].is_split()) m_triangles[facet_idx].set_state(type); tr = &m_triangles[facet_idx]; // might have been invalidated by split_triangle(). @@ -2208,4 +2211,259 @@ std::vector TriangleSelector::extract_used_facet_states(con return out; } +inline bool segments_intersect_proj( + const Vec3f& p1, const Vec3f& p2, const Vec3f& p3, const Vec3f& p4, const std::function(const Vec3f&)>& proj) +{ + auto cross2d = [](float ax, float ay, float bx, float by) -> float { return ax * by - ay * bx; }; + auto [u1, v1] = proj(p1); + auto [u2, v2] = proj(p2); + auto [u3, v3] = proj(p3); + auto [u4, v4] = proj(p4); + float ru = u2 - u1, rv = v2 - v1; + float su = u4 - u3, sv = v4 - v3; + float denom = cross2d(ru, rv, su, sv); + if (std::abs(denom) < 1e-10f) + return false; + float dpu = u3 - u1, dpv = v3 - v1; + float t1 = cross2d(dpu, dpv, su, sv) / denom; + float t2 = cross2d(dpu, dpv, ru, rv) / denom; + return 0.f <= t1 && t1 <= 1.0f && 0.f <= t2 && t2 <= 1.f; +}; + +class TriangleCursor : public TriangleSelector::SinglePointCursor +{ +public: + TriangleCursor() = delete; + explicit TriangleCursor(const Vec3f& center_, + const Vec3f& source_, + float radius_world, + const Transform3d& trafo_, + const TriangleSelector::ClippingPlane& clipping_plane_, + const std::array& vertices) + : TriangleSelector::SinglePointCursor(center_, source_, radius_world, trafo_, clipping_plane_) + , vertices_(vertices) + {} + ~TriangleCursor() override = default; + + static std::unique_ptr build_cursor(const TriangleSelector& source_selector, const TriangleSelector::Triangle& tri) + { + const Vec3f& pv0 = source_selector.m_vertices[tri.verts_idxs[0]].v; + const Vec3f& pv1 = source_selector.m_vertices[tri.verts_idxs[1]].v; + const Vec3f& pv2 = source_selector.m_vertices[tri.verts_idxs[2]].v; + + // Calculate the centroid of the triangle + const Vec3f center = (pv0 + pv1 + pv2) / 3.f; + + // Calculate the norm of the plane + const Vec3f& norm = source_selector.m_face_normals[tri.source_triangle]; + + // Calculate the min distance from the centroid to every edges + const float radius = std::max(std::min(std::min(point_to_line_dist(center, pv0, pv1), point_to_line_dist(center, pv0, pv2)), + point_to_line_dist(center, pv1, pv2)), 0.1f); + + return std::make_unique(center, center + norm, radius, Transform3d::Identity(), TriangleSelector::ClippingPlane(), + std::array{pv0, pv1, pv2}); + } + + bool is_mesh_point_inside(const Vec3f& point) const override + { + return is_point_inside_triangle(point, vertices_[0], vertices_[1], vertices_[2]); + } + + bool is_edge_inside_cursor(const TriangleSelector::Triangle& tr, const std::vector& vertices) const override + { + std::array pts_proj; // Projected point onto the plane + std::array pts_dist; // Distance to the plane, positive means above, negative means below the plane + for (int i = 0; i < 3; ++i) { + Vec3f p = vertices[tr.verts_idxs[i]].v; + if (!this->uniform_scaling) + p = this->trafo * p; + + const Vec3f diff = p - center; + pts_dist[i] = diff.dot(dir); + pts_proj[i] = p - pts_dist[i] * dir; + } + + for (int side = 0; side < 3; ++side) { + const int idx_a = side; + const int idx_b = side < 2 ? side + 1 : 0; + + // If both ends at the same side and farther than tolerance, skip + const float dist_a = pts_dist[idx_a]; + const float dist_b = pts_dist[idx_b]; + if ((dist_a > tolerance_ && dist_b > tolerance_) || (dist_a < -tolerance_ && dist_b < -tolerance_)) { + continue; + } + + // Find the projected line segment which has distance to the plane within the tolerance + Vec3f pt_a = pts_proj[idx_a]; + Vec3f pt_b = pts_proj[idx_b]; + if (std::abs(dist_a) > tolerance_) { + pt_a = (tolerance_ - dist_b) / (dist_a - dist_b) * (pts_proj[idx_a] - pts_proj[idx_b]); + } + if (std::abs(dist_b) > tolerance_) { + pt_b = (tolerance_ - dist_a) / (dist_b - dist_a) * (pts_proj[idx_b] - pts_proj[idx_a]); + } + + // If any projected end is inside the triangle, then is in + if (is_point_inside_triangle(pt_a, vertices_[0], vertices_[1], vertices_[2]) || + is_point_inside_triangle(pt_b, vertices_[0], vertices_[1], vertices_[2])) { + return true; + } + + // Otherwise see if the segment (pt_a, pt_b) intersects the triangle + { + const Vec3f uvw_a = barycentric(pt_a, vertices_[0], vertices_[1], vertices_[2]); + const Vec3f uvw_b = barycentric(pt_b, vertices_[0], vertices_[1], vertices_[2]); + auto proj = [&](const Vec3f& p) -> std::pair { return {p(0), p(1)}; }; + const Vec3f uvw_0 = {1.f,0.f,0.f}; + const Vec3f uvw_1 = {0.f,1.f,0.f}; + const Vec3f uvw_2 = {0.f,0.f,1.f}; + + if (segments_intersect_proj(uvw_a, uvw_b, uvw_0, uvw_1, proj)|| + segments_intersect_proj(uvw_a, uvw_b, uvw_0, uvw_2, proj)|| + segments_intersect_proj(uvw_a, uvw_b, uvw_1, uvw_2, proj)) { + return true; + } + } + } + return false; + } + + bool is_facet_visible(int facet_idx, const std::vector& face_normals) const override + { + return TriangleSelector::Cursor::is_facet_visible(*this, facet_idx, face_normals); + } + +private: + const std::array vertices_; + const float tolerance_ = EPSILON; + const float tolerance_sqr_ = tolerance_ * tolerance_; + + static float point_to_line_dist(const Vec3f& p, const Vec3f& a, const Vec3f& b) + { + const Eigen::ParametrizedLine line = Eigen::ParametrizedLine::Through(a, b); + return line.distance(p); + } +}; + +// Remap painting data from source mesh to target mesh using spatial mapping. +TriangleSelector::TriangleSplittingData TriangleSelector::remap_painting( + const indexed_triangle_set& source_its, + const TriangleSplittingData& source_painting, + const indexed_triangle_set& target_its) +{ + TriangleSelector::TriangleSplittingData result; + if (source_painting.bitstream.empty()) + return result; + + // 1. Deserialize source painting + TriangleMesh source_mesh(source_its); + TriangleSelector source_selector(source_mesh); + source_selector.deserialize(source_painting, false); + + // 2. Extract painted geometry + std::vector> painted_triangles; + painted_triangles.reserve(source_selector.m_triangles.size()); + for (const Triangle& tr : source_selector.m_triangles) { + if (tr.valid() && !tr.is_split() && tr.get_state() != EnforcerBlockerType::NONE) { + painted_triangles.push_back(std::ref(tr)); + } + } + + if (painted_triangles.empty()) + return result; + + // 3. Build AABB tree of target mesh so we could find nearest face quickly + AABBTreeIndirect::Tree3f target_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(target_its.vertices, target_its.indices); + + // Helper: check overlap between a paint triangle and a target triangle. + // Uses 3D barycentric point-in-triangle tests and dominant-axis 2D projection + // for edge-edge intersection to handle all triangle orientations correctly. + auto check_overlap = [&](const Vec3f& pa, const Vec3f& pb, const Vec3f& pc, + const Vec3f& ta, const Vec3f& tb, const Vec3f& tc) -> bool { + // Check if any target vertex is inside the paint triangle + if (is_point_inside_triangle(ta, pa, pb, pc)) return true; + if (is_point_inside_triangle(tb, pa, pb, pc)) return true; + if (is_point_inside_triangle(tc, pa, pb, pc)) return true; + + // Check if any paint vertex is inside the target triangle + if (is_point_inside_triangle(pa, ta, tb, tc)) return true; + if (is_point_inside_triangle(pb, ta, tb, tc)) return true; + if (is_point_inside_triangle(pc, ta, tb, tc)) return true; + + // Check edge-edge intersections using dominant-axis 2D projection. + // Project onto the plane (XY, XZ, or YZ) where the triangles have the + // most area, avoiding degenerate projections for vertical/angled surfaces. + Vec3f n1 = (pb - pa).cross(pc - pa); + Vec3f n2 = (tb - ta).cross(tc - ta); + Vec3f n_abs = (n1.cwiseAbs() + n2.cwiseAbs()); + int axis = (n_abs.x() >= n_abs.y() && n_abs.x() >= n_abs.z()) ? 0 + : (n_abs.y() >= n_abs.z()) ? 1 : 2; + int u_axis = (axis + 1) % 3; + int v_axis = (axis + 2) % 3; + + auto proj = [&](const Vec3f& p) -> std::pair { + return { p(u_axis), p(v_axis) }; + }; + + if (segments_intersect_proj(pa, pb, ta, tb, proj)) return true; + if (segments_intersect_proj(pa, pb, tb, tc, proj)) return true; + if (segments_intersect_proj(pa, pb, tc, ta, proj)) return true; + if (segments_intersect_proj(pb, pc, ta, tb, proj)) return true; + if (segments_intersect_proj(pb, pc, tb, tc, proj)) return true; + if (segments_intersect_proj(pb, pc, tc, ta, proj)) return true; + if (segments_intersect_proj(pc, pa, ta, tb, proj)) return true; + if (segments_intersect_proj(pc, pa, tb, tc, proj)) return true; + if (segments_intersect_proj(pc, pa, tc, ta, proj)) return true; + + return false; + }; + + // 4. For each painted face, we find the nearest target face, and apply the TriangleCursor to paint it + TriangleMesh target_mesh(target_its); + TriangleSelector target_selector(target_mesh); + const auto facet_angle_limit = cos(Geometry::deg2rad(5)); + auto check_normal = [&source_selector, &target_selector, facet_angle_limit](int src_idx, int dst_idx) -> bool { + const Vec3f& norm_a = source_selector.m_face_normals[src_idx]; + const Vec3f& norm_b = target_selector.m_face_normals[dst_idx]; + const float a = norm_a.dot(norm_b); + + return std::clamp(a, 0.f, 1.f) >= facet_angle_limit; + }; + for (auto tri_ref : painted_triangles) { + const Triangle& tri = tri_ref.get(); + const Vec3f& pv0 = source_selector.m_vertices[tri.verts_idxs[0]].v; + const Vec3f& pv1 = source_selector.m_vertices[tri.verts_idxs[1]].v; + const Vec3f& pv2 = source_selector.m_vertices[tri.verts_idxs[2]].v; + + // Find ALL target faces whose bounding boxes overlap with the paint + // triangle's bounding box, not just the nearest one. + Eigen::AlignedBox3f pt_bbox; + pt_bbox.extend(pv0); + pt_bbox.extend(pv1); + pt_bbox.extend(pv2); + + AABBTreeIndirect::traverse(target_tree, AABBTreeIndirect::intersecting(pt_bbox), [&](const AABBTreeIndirect::Tree3f::Node& node) -> bool { + size_t face_idx = node.idx; + if (face_idx >= target_its.indices.size()) + return true; + + const Vec3i32& face = target_its.indices[face_idx]; + const Vec3f& ta = target_its.vertices[face(0)]; + const Vec3f& tb = target_its.vertices[face(1)]; + const Vec3f& tc = target_its.vertices[face(2)]; + + if (check_normal(tri.source_triangle, face_idx) && check_overlap(pv0, pv1, pv2, ta, tb, tc)) { + // Paint this face + target_selector.select_patch(face_idx, TriangleCursor::build_cursor(source_selector, tri), tri.get_state(), + Transform3d::Identity(), true); + } + return true; // continue traversal + }); + } + + return target_selector.serialize(); +} + } // namespace Slic3r diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index 50bbdd4ed0..99ad19548e 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -372,6 +372,13 @@ public: // The operation may merge split triangles if they are being assigned the same color. void seed_fill_apply_on_triangles(EnforcerBlockerType new_state); + // Remap painting data from source mesh to target mesh using spatial mapping. + // Both meshes must be in the same coordinate space. + static TriangleSplittingData remap_painting( + const indexed_triangle_set& source_its, + const TriangleSplittingData& source_painting, + const indexed_triangle_set& target_its); + protected: // Triangle and info about how it's split. class Triangle { @@ -521,11 +528,10 @@ private: int m_free_triangles_head { -1 }; int m_free_vertices_head { -1 }; + + friend class TriangleCursor; }; - - - } // namespace Slic3r #endif // libslic3r_TriangleSelector_hpp_ From e451df27f792bf6455121a72c55c384dcd34ba32 Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Tue, 5 May 2026 11:05:48 +0800 Subject: [PATCH 004/117] Fix issue of pojection calculation --- src/libslic3r/TriangleSelector.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 6368955fce..ba6926d227 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -2299,10 +2299,10 @@ public: Vec3f pt_a = pts_proj[idx_a]; Vec3f pt_b = pts_proj[idx_b]; if (std::abs(dist_a) > tolerance_) { - pt_a = (tolerance_ - dist_b) / (dist_a - dist_b) * (pts_proj[idx_a] - pts_proj[idx_b]); + pt_a = (tolerance_ - dist_b) / (dist_a - dist_b) * (pts_proj[idx_a] - pts_proj[idx_b]) + pts_proj[idx_b]; } if (std::abs(dist_b) > tolerance_) { - pt_b = (tolerance_ - dist_a) / (dist_b - dist_a) * (pts_proj[idx_b] - pts_proj[idx_a]); + pt_b = (tolerance_ - dist_a) / (dist_b - dist_a) * (pts_proj[idx_b] - pts_proj[idx_a]) + pts_proj[idx_a]; } // If any projected end is inside the triangle, then is in @@ -2315,7 +2315,7 @@ public: { const Vec3f uvw_a = barycentric(pt_a, vertices_[0], vertices_[1], vertices_[2]); const Vec3f uvw_b = barycentric(pt_b, vertices_[0], vertices_[1], vertices_[2]); - auto proj = [&](const Vec3f& p) -> std::pair { return {p(0), p(1)}; }; + auto proj = [](const Vec3f& p) -> std::pair { return {p(0), p(1)}; }; const Vec3f uvw_0 = {1.f,0.f,0.f}; const Vec3f uvw_1 = {0.f,1.f,0.f}; const Vec3f uvw_2 = {0.f,0.f,1.f}; From 4b489339c623e8d66a1154686aba000147066a37 Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Tue, 5 May 2026 11:59:20 +0800 Subject: [PATCH 005/117] Check normal before expanding the painting to neighboring faces --- src/libslic3r/TriangleSelector.cpp | 33 +++++++++++++++++------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index ba6926d227..a38220e2a5 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -171,7 +171,7 @@ void TriangleSelector::Triangle::set_division(int sides_to_split, int special_si } // Real-time collision detection, Ericson, Chapter 3.4 -inline Vec3f barycentric(const Vec3f& pt, const Vec3f& p1, const Vec3f& p2, const Vec3f& p3) +static Vec3f barycentric(const Vec3f& pt, const Vec3f& p1, const Vec3f& p2, const Vec3f& p3) { std::array v = {p2 - p1, p3 - p1, pt - p1}; float d00 = v[0].dot(v[0]); @@ -186,7 +186,7 @@ inline Vec3f barycentric(const Vec3f& pt, const Vec3f& p1, const Vec3f& p2, cons return barycentric_cords; } -inline bool is_point_inside_triangle(const Vec3f &pt, const Vec3f &p1, const Vec3f &p2, const Vec3f &p3) +static bool is_point_inside_triangle(const Vec3f &pt, const Vec3f &p1, const Vec3f &p2, const Vec3f &p3) { Vec3f barycentric_cords = barycentric(pt, p1, p2, p3); return std::all_of(begin(barycentric_cords), end(barycentric_cords), [](float cord) { return 0.f <= cord && cord <= 1.0; }); @@ -2211,8 +2211,7 @@ std::vector TriangleSelector::extract_used_facet_states(con return out; } -inline bool segments_intersect_proj( - const Vec3f& p1, const Vec3f& p2, const Vec3f& p3, const Vec3f& p4, const std::function(const Vec3f&)>& proj) +static bool segments_intersect_proj(const Vec3f& p1, const Vec3f& p2, const Vec3f& p3, const Vec3f& p4, const std::function(const Vec3f&)>& proj) { auto cross2d = [](float ax, float ay, float bx, float by) -> float { return ax * by - ay * bx; }; auto [u1, v1] = proj(p1); @@ -2332,13 +2331,22 @@ public: bool is_facet_visible(int facet_idx, const std::vector& face_normals) const override { - return TriangleSelector::Cursor::is_facet_visible(*this, facet_idx, face_normals); + const Vec3f& n = face_normals[facet_idx]; + + return check_normal(n, this->dir); + } + + static bool check_normal(const Vec3f& facet_norm, const Vec3f& camera_dir) + { + const float a = -(facet_norm.dot(camera_dir)); + return std::clamp(a, 0.f, 1.f) >= facet_angle_limit; } private: const std::array vertices_; const float tolerance_ = EPSILON; const float tolerance_sqr_ = tolerance_ * tolerance_; + static const double facet_angle_limit; static float point_to_line_dist(const Vec3f& p, const Vec3f& a, const Vec3f& b) { @@ -2346,6 +2354,8 @@ private: return line.distance(p); } }; +const double TriangleCursor::facet_angle_limit = cos(Geometry::deg2rad(5)); + // Remap painting data from source mesh to target mesh using spatial mapping. TriangleSelector::TriangleSplittingData TriangleSelector::remap_painting( @@ -2423,14 +2433,6 @@ TriangleSelector::TriangleSplittingData TriangleSelector::remap_painting( // 4. For each painted face, we find the nearest target face, and apply the TriangleCursor to paint it TriangleMesh target_mesh(target_its); TriangleSelector target_selector(target_mesh); - const auto facet_angle_limit = cos(Geometry::deg2rad(5)); - auto check_normal = [&source_selector, &target_selector, facet_angle_limit](int src_idx, int dst_idx) -> bool { - const Vec3f& norm_a = source_selector.m_face_normals[src_idx]; - const Vec3f& norm_b = target_selector.m_face_normals[dst_idx]; - const float a = norm_a.dot(norm_b); - - return std::clamp(a, 0.f, 1.f) >= facet_angle_limit; - }; for (auto tri_ref : painted_triangles) { const Triangle& tri = tri_ref.get(); const Vec3f& pv0 = source_selector.m_vertices[tri.verts_idxs[0]].v; @@ -2449,12 +2451,15 @@ TriangleSelector::TriangleSplittingData TriangleSelector::remap_painting( if (face_idx >= target_its.indices.size()) return true; + const Vec3f& norm_a = source_selector.m_face_normals[tri.source_triangle]; + const Vec3f& norm_b = target_selector.m_face_normals[face_idx]; + const Vec3i32& face = target_its.indices[face_idx]; const Vec3f& ta = target_its.vertices[face(0)]; const Vec3f& tb = target_its.vertices[face(1)]; const Vec3f& tc = target_its.vertices[face(2)]; - if (check_normal(tri.source_triangle, face_idx) && check_overlap(pv0, pv1, pv2, ta, tb, tc)) { + if (TriangleCursor::check_normal(norm_b, -norm_a) && check_overlap(pv0, pv1, pv2, ta, tb, tc)) { // Paint this face target_selector.select_patch(face_idx, TriangleCursor::build_cursor(source_selector, tri), tri.get_state(), Transform3d::Identity(), true); From 01e0013b1656c4e622c4d9d5090868b751ea5da2 Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Tue, 5 May 2026 17:08:53 +0800 Subject: [PATCH 006/117] Don't remap paint unless it's the final cut --- src/libslic3r/CutUtils.cpp | 47 ++++++++++++++-------------- src/libslic3r/CutUtils.hpp | 2 +- src/libslic3r/Model.hpp | 1 + src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 3 +- 4 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/libslic3r/CutUtils.cpp b/src/libslic3r/CutUtils.cpp index 7979dc4095..b69c4cf4ee 100644 --- a/src/libslic3r/CutUtils.cpp +++ b/src/libslic3r/CutUtils.cpp @@ -14,9 +14,9 @@ namespace Slic3r { using namespace Geometry; -// Saved painting data for remapping after mesh cutting. +// Saved painting data for remapping after mesh change. struct SavedPainting { - indexed_triangle_set its; // Original mesh transformed to cut space + indexed_triangle_set its; // Original mesh TriangleSelector::TriangleSplittingData supported; TriangleSelector::TriangleSplittingData seam; TriangleSelector::TriangleSplittingData mmu; @@ -24,21 +24,24 @@ struct SavedPainting { }; // Remap painting data from saved source to a cut result mesh, and set on a volume. -static void remap_and_set_painting(ModelVolume* vol, const indexed_triangle_set& cut_its, - const SavedPainting& saved) +static void remap_and_set_painting(ModelVolume* vol, const SavedPainting* saved) { + if (!saved) { + return; + } + auto remap_one = [&](const TriangleSelector::TriangleSplittingData& src_data, FacetsAnnotation& target_facets) { if (src_data.bitstream.empty()) return; - auto result = TriangleSelector::remap_painting(saved.its, src_data, cut_its); + auto result = TriangleSelector::remap_painting(saved->its, src_data, vol->mesh().its); if (!result.bitstream.empty()) target_facets.set_data(result); }; - remap_one(saved.supported, vol->supported_facets); - remap_one(saved.seam, vol->seam_facets); - remap_one(saved.mmu, vol->mmu_segmentation_facets); - remap_one(saved.fuzzy, vol->fuzzy_skin_facets); + remap_one(saved->supported, vol->supported_facets); + remap_one(saved->seam, vol->seam_facets); + remap_one(saved->mmu, vol->mmu_segmentation_facets); + remap_one(saved->fuzzy, vol->fuzzy_skin_facets); } static void apply_tolerance(ModelVolume* vol) @@ -217,28 +220,26 @@ static void process_solid_part_cut(ModelVolume* volume, const Transform3d& insta // Add required cut parts to the objects if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) { - add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A"); - if (saved_painting && !upper_mesh.empty()) - remap_and_set_painting(upper->volumes.back(), upper_mesh.its, *saved_painting); + if (!upper_mesh.empty()) { + add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A"); + remap_and_set_painting(upper->volumes.back(), saved_painting); + } if (!lower_mesh.empty()) { add_cut_volume(lower_mesh, upper, volume, cut_matrix, "_B"); - if (saved_painting) - remap_and_set_painting(upper->volumes.back(), lower_mesh.its, *saved_painting); upper->volumes.back()->cut_info.is_from_upper = false; + remap_and_set_painting(upper->volumes.back(), saved_painting); } return; } - if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { + if (attributes.has(ModelObjectCutAttribute::KeepUpper) && !upper_mesh.empty()) { add_cut_volume(upper_mesh, upper, volume, cut_matrix); - if (saved_painting && !upper_mesh.empty()) - remap_and_set_painting(upper->volumes.back(), upper_mesh.its, *saved_painting); + remap_and_set_painting(upper->volumes.back(), saved_painting); } if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty()) { add_cut_volume(lower_mesh, lower, volume, cut_matrix); - if (saved_painting) - remap_and_set_painting(lower->volumes.back(), lower_mesh.its, *saved_painting); + remap_and_set_painting(lower->volumes.back(), saved_painting); } } @@ -363,21 +364,19 @@ const ModelObjectPtrs& Cut::perform_with_plane() // Save painting data before reset_extra_facets() discards it. // Only for model parts that will be cut (not modifiers/connectors). std::optional saved_painting; - if (volume->is_model_part() && !volume->mesh().empty()) { - SavedPainting sp; + if (m_attributes.has(ModelObjectCutAttribute::KeepPaint) && volume->is_any_painted() && volume->is_model_part() && !volume->mesh().empty()) { // Get mesh in cut space (same transform as process_volume_cut applies) TriangleMesh mesh(volume->mesh()); const auto volume_matrix = volume->get_matrix(); mesh.transform(inverse_cut_matrix * instance_matrix * volume_matrix, true); mesh.transform(m_cut_matrix); + SavedPainting sp; sp.its = std::move(mesh.its); sp.supported = volume->supported_facets.get_data(); sp.seam = volume->seam_facets.get_data(); sp.mmu = volume->mmu_segmentation_facets.get_data(); sp.fuzzy = volume->fuzzy_skin_facets.get_data(); - if (!sp.supported.bitstream.empty() || !sp.seam.bitstream.empty() || - !sp.mmu.bitstream.empty() || !sp.fuzzy.bitstream.empty()) - saved_painting = std::move(sp); + saved_painting = std::move(sp); } volume->reset_extra_facets(); diff --git a/src/libslic3r/CutUtils.hpp b/src/libslic3r/CutUtils.hpp index 2c477a3e2b..f39007c8a9 100644 --- a/src/libslic3r/CutUtils.hpp +++ b/src/libslic3r/CutUtils.hpp @@ -11,7 +11,7 @@ namespace Slic3r { using ModelObjectPtrs = std::vector; -enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, KeepAsParts, FlipUpper, FlipLower, PlaceOnCutUpper, PlaceOnCutLower, CreateDowels, InvalidateCutInfo }; +enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, KeepAsParts, FlipUpper, FlipLower, PlaceOnCutUpper, PlaceOnCutLower, CreateDowels, InvalidateCutInfo, KeepPaint }; using ModelObjectCutAttributes = enum_bitmask; ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 302bf47935..0de76dbef4 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -1005,6 +1005,7 @@ public: bool is_seam_painted() const { return !this->seam_facets.empty(); } bool is_mm_painted() const { return !this->mmu_segmentation_facets.empty(); } bool is_fuzzy_skin_painted() const { return !this->fuzzy_skin_facets.empty(); } + bool is_any_painted() const { return is_fdm_support_painted() || is_seam_painted() || is_mm_painted() || is_fuzzy_skin_painted(); } // Orca: Implement prusa's filament shrink compensation approach // Returns 0-based indices of extruders painted by multi-material painting gizmo. diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 2483f7bd1b..59c14ef205 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -3329,7 +3329,8 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) only_if(m_rotate_upper, ModelObjectCutAttribute::FlipUpper) | only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower) | only_if(dowels_count > 0, ModelObjectCutAttribute::CreateDowels) | - only_if(!has_connectors && !cut_with_groove && cut_mo->cut_id.id().invalid(), ModelObjectCutAttribute::InvalidateCutInfo); + only_if(!has_connectors && !cut_with_groove && cut_mo->cut_id.id().invalid(), ModelObjectCutAttribute::InvalidateCutInfo) | + ModelObjectCutAttribute::KeepPaint; // update cut_id for the cut object in respect to the attributes update_object_cut_id(cut_mo->cut_id, attributes, dowels_count); From 368563e14ef26bae0bc9463b73482fae1cc57781 Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Tue, 5 May 2026 17:55:38 +0800 Subject: [PATCH 007/117] Fix part offset --- src/libslic3r/CutUtils.cpp | 2 +- src/libslic3r/TriangleSelector.cpp | 18 ++++++++++-------- src/libslic3r/TriangleSelector.hpp | 5 +++-- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/CutUtils.cpp b/src/libslic3r/CutUtils.cpp index b69c4cf4ee..16f0a19816 100644 --- a/src/libslic3r/CutUtils.cpp +++ b/src/libslic3r/CutUtils.cpp @@ -34,7 +34,7 @@ static void remap_and_set_painting(ModelVolume* vol, const SavedPainting* saved) FacetsAnnotation& target_facets) { if (src_data.bitstream.empty()) return; - auto result = TriangleSelector::remap_painting(saved->its, src_data, vol->mesh().its); + auto result = TriangleSelector::remap_painting(saved->its, src_data, vol->mesh().its, translation_transform(vol->mesh().get_init_shift())); if (!result.bitstream.empty()) target_facets.set_data(result); }; diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index a38220e2a5..d4821df7cd 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -2361,7 +2361,8 @@ const double TriangleCursor::facet_angle_limit = cos(Geometry::deg2rad(5)); TriangleSelector::TriangleSplittingData TriangleSelector::remap_painting( const indexed_triangle_set& source_its, const TriangleSplittingData& source_painting, - const indexed_triangle_set& target_its) + const indexed_triangle_set& target_its, + const Transform3d& target_transform) { TriangleSelector::TriangleSplittingData result; if (source_painting.bitstream.empty()) @@ -2385,7 +2386,9 @@ TriangleSelector::TriangleSplittingData TriangleSelector::remap_painting( return result; // 3. Build AABB tree of target mesh so we could find nearest face quickly - AABBTreeIndirect::Tree3f target_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(target_its.vertices, target_its.indices); + TriangleMesh target_mesh(target_its); + target_mesh.transform(target_transform); + AABBTreeIndirect::Tree3f target_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(target_mesh.its.vertices, target_mesh.its.indices); // Helper: check overlap between a paint triangle and a target triangle. // Uses 3D barycentric point-in-triangle tests and dominant-axis 2D projection @@ -2431,7 +2434,6 @@ TriangleSelector::TriangleSplittingData TriangleSelector::remap_painting( }; // 4. For each painted face, we find the nearest target face, and apply the TriangleCursor to paint it - TriangleMesh target_mesh(target_its); TriangleSelector target_selector(target_mesh); for (auto tri_ref : painted_triangles) { const Triangle& tri = tri_ref.get(); @@ -2448,16 +2450,16 @@ TriangleSelector::TriangleSplittingData TriangleSelector::remap_painting( AABBTreeIndirect::traverse(target_tree, AABBTreeIndirect::intersecting(pt_bbox), [&](const AABBTreeIndirect::Tree3f::Node& node) -> bool { size_t face_idx = node.idx; - if (face_idx >= target_its.indices.size()) + if (face_idx >= target_mesh.its.indices.size()) return true; const Vec3f& norm_a = source_selector.m_face_normals[tri.source_triangle]; const Vec3f& norm_b = target_selector.m_face_normals[face_idx]; - const Vec3i32& face = target_its.indices[face_idx]; - const Vec3f& ta = target_its.vertices[face(0)]; - const Vec3f& tb = target_its.vertices[face(1)]; - const Vec3f& tc = target_its.vertices[face(2)]; + const Vec3i32& face = target_mesh.its.indices[face_idx]; + const Vec3f& ta = target_mesh.its.vertices[face(0)]; + const Vec3f& tb = target_mesh.its.vertices[face(1)]; + const Vec3f& tc = target_mesh.its.vertices[face(2)]; if (TriangleCursor::check_normal(norm_b, -norm_a) && check_overlap(pv0, pv1, pv2, ta, tb, tc)) { // Paint this face diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index 99ad19548e..8f02c7d717 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -373,11 +373,12 @@ public: void seed_fill_apply_on_triangles(EnforcerBlockerType new_state); // Remap painting data from source mesh to target mesh using spatial mapping. - // Both meshes must be in the same coordinate space. + // `target_transform` should transform the target mesh into source's coordinate space. static TriangleSplittingData remap_painting( const indexed_triangle_set& source_its, const TriangleSplittingData& source_painting, - const indexed_triangle_set& target_its); + const indexed_triangle_set& target_its, + const Transform3d& target_transform); protected: // Triangle and info about how it's split. From 5978e96ff5a8b643afc85e899bf53a788f646726 Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Tue, 5 May 2026 18:39:50 +0800 Subject: [PATCH 008/117] Add option to enable/disable paint keep --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 2 +- src/slic3r/GUI/Preferences.cpp | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 59c14ef205..bfc757589c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -3330,7 +3330,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower) | only_if(dowels_count > 0, ModelObjectCutAttribute::CreateDowels) | only_if(!has_connectors && !cut_with_groove && cut_mo->cut_id.id().invalid(), ModelObjectCutAttribute::InvalidateCutInfo) | - ModelObjectCutAttribute::KeepPaint; + only_if(wxGetApp().app_config->get_bool("keep_painting"),ModelObjectCutAttribute::KeepPaint); // update cut_id for the cut object in respect to the attributes update_object_cut_id(cut_mo->cut_id, attributes, dowels_count); diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index c5d377b67b..fc27d27e31 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -956,7 +956,7 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " sync_user_preset: " << (sync ? "true" : "false"); } - #ifdef __WXMSW__ +#ifdef __WXMSW__ if (param == "associate_3mf") { bool pbool = app_config->get("associate_3mf") == "true" ? true : false; if (pbool) { @@ -1776,6 +1776,9 @@ void PreferencesDialog::create_items() auto item_ams_blacklist = create_item_checkbox(_L("Skip AMS blacklist check"), "", "skip_ams_blacklist_check"); g_sizer->Add(item_ams_blacklist); + auto item_keep_painting = create_item_checkbox(_L("(Experimental) Keep painted feature after mesh change"), _L("Attempt to keep painted features (color/seam/support/fuzzy etc.) after changing the object mesh (such as cut/reload from disk/simplify/fix etc.)\nHighly experimental! Slow and may create artifact."), "keep_painting"); + g_sizer->Add(item_keep_painting); + g_sizer->Add(create_item_title(_L("Storage")), 1, wxEXPAND); auto item_allow_abnormal_storage = create_item_checkbox(_L("Allow Abnormal Storage"), _L("This allows the use of Storage that is marked as abnormal by the Printer.\nUse at your own risk, can cause issues!"), "allow_abnormal_storage"); g_sizer->Add(item_allow_abnormal_storage); From be141d32c18a16e7aaa5b15f89af06f62a97d721 Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Tue, 5 May 2026 21:43:29 +0800 Subject: [PATCH 009/117] Speed up calculation by caching barycentric independent variables --- src/libslic3r/TriangleSelector.cpp | 124 +++++++++++++++++++---------- 1 file changed, 83 insertions(+), 41 deletions(-) diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index d4821df7cd..e4d1a4521c 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -170,25 +170,69 @@ void TriangleSelector::Triangle::set_division(int sides_to_split, int special_si this->special_side_idx = char(special_side_idx); } +// Pre-computed barycentric resolver // Real-time collision detection, Ericson, Chapter 3.4 -static Vec3f barycentric(const Vec3f& pt, const Vec3f& p1, const Vec3f& p2, const Vec3f& p3) +// Cache inspired by Don Hatch at https://gamedev.stackexchange.com/questions/23743/whats-the-most-efficient-way-to-find-barycentric-coordinates/23745#comment390123_23745 +struct Barycentric { - std::array v = {p2 - p1, p3 - p1, pt - p1}; - float d00 = v[0].dot(v[0]); - float d01 = v[0].dot(v[1]); - float d11 = v[1].dot(v[1]); - float d20 = v[2].dot(v[0]); - float d21 = v[2].dot(v[1]); - float denom = d00 * d11 - d01 * d01; +public: + Barycentric(const Vec3f& a, const Vec3f& b, const Vec3f& c) + :a_(a) + { + // Pre-compute denominator + const Vec3f v0 = b - a; + const Vec3f v1 = c - a; + const float d00 = v0.dot(v0); + const float d01 = v0.dot(v1); + const float d11 = v1.dot(v1); + const float inv_denom_ = 1.0f / (d00 * d11 - d01 * d01); + + x1_ = (d11 * v0 - d01 * v1) * inv_denom_; + x2_ = (d00 * v1 - d01 * v0) * inv_denom_; + } - Vec3f barycentric_cords(1.f, (d11 * d20 - d01 * d21) / denom, (d00 * d21 - d01 * d20) / denom); - barycentric_cords.x() = barycentric_cords.x() - barycentric_cords.y() - barycentric_cords.z(); - return barycentric_cords; -} + Vec3f calc(const Vec3f& p) const + { + const Vec3f v2 = p - a_; + const float v = v2.dot(x1_); + const float w = v2.dot(x2_); + const float u = 1.f - v - w; + + return {u, v, w}; + } + + bool is_point_inside_triangle(const Vec3f& p) const + { + const Vec3f barycentric_cords = calc(p); + return std::all_of(begin(barycentric_cords), end(barycentric_cords), [](float cord) { return 0.f <= cord && cord <= 1.0; }); + } + + static Vec3f calc(const Vec3f& pt, const Vec3f& p1, const Vec3f& p2, const Vec3f& p3) + { + const std::array vec = {p2 - p1, p3 - p1, pt - p1}; + const float d00 = vec[0].dot(vec[0]); + const float d01 = vec[0].dot(vec[1]); + const float d11 = vec[1].dot(vec[1]); + const float d20 = vec[2].dot(vec[0]); + const float d21 = vec[2].dot(vec[1]); + const float denom = d00 * d11 - d01 * d01; + + const float v = (d11 * d20 - d01 * d21) / denom; + const float w = (d00 * d21 - d01 * d20) / denom; + const float u = 1.f - v - w; + + return {u, v, w}; + } + +private: + Vec3f a_; + Vec3f x1_; + Vec3f x2_; +}; static bool is_point_inside_triangle(const Vec3f &pt, const Vec3f &p1, const Vec3f &p2, const Vec3f &p3) { - Vec3f barycentric_cords = barycentric(pt, p1, p2, p3); + Vec3f barycentric_cords = Barycentric::calc(pt, p1, p2, p3); return std::all_of(begin(barycentric_cords), end(barycentric_cords), [](float cord) { return 0.f <= cord && cord <= 1.0; }); } @@ -2211,21 +2255,22 @@ std::vector TriangleSelector::extract_used_facet_states(con return out; } -static bool segments_intersect_proj(const Vec3f& p1, const Vec3f& p2, const Vec3f& p3, const Vec3f& p4, const std::function(const Vec3f&)>& proj) +static bool segments_intersect_proj(const Vec3f& p1, const Vec3f& p2, const Vec3f& p3, const Vec3f& p4, const std::pair& proj) { auto cross2d = [](float ax, float ay, float bx, float by) -> float { return ax * by - ay * bx; }; - auto [u1, v1] = proj(p1); - auto [u2, v2] = proj(p2); - auto [u3, v3] = proj(p3); - auto [u4, v4] = proj(p4); - float ru = u2 - u1, rv = v2 - v1; - float su = u4 - u3, sv = v4 - v3; - float denom = cross2d(ru, rv, su, sv); + const auto [u_axis, v_axis] = proj; + const auto u1 = p1(u_axis), v1 = p1(v_axis); + const auto u2 = p2(u_axis), v2 = p2(v_axis); + const auto u3 = p3(u_axis), v3 = p3(v_axis); + const auto u4 = p4(u_axis), v4 = p4(v_axis); + const float ru = u2 - u1, rv = v2 - v1; + const float su = u4 - u3, sv = v4 - v3; + const float denom = cross2d(ru, rv, su, sv); if (std::abs(denom) < 1e-10f) return false; - float dpu = u3 - u1, dpv = v3 - v1; - float t1 = cross2d(dpu, dpv, su, sv) / denom; - float t2 = cross2d(dpu, dpv, ru, rv) / denom; + const float dpu = u3 - u1, dpv = v3 - v1; + const float t1 = cross2d(dpu, dpv, su, sv) / denom; + const float t2 = cross2d(dpu, dpv, ru, rv) / denom; return 0.f <= t1 && t1 <= 1.0f && 0.f <= t2 && t2 <= 1.f; }; @@ -2240,7 +2285,7 @@ public: const TriangleSelector::ClippingPlane& clipping_plane_, const std::array& vertices) : TriangleSelector::SinglePointCursor(center_, source_, radius_world, trafo_, clipping_plane_) - , vertices_(vertices) + , barycentric_(vertices[0], vertices[1], vertices[2]) {} ~TriangleCursor() override = default; @@ -2266,7 +2311,7 @@ public: bool is_mesh_point_inside(const Vec3f& point) const override { - return is_point_inside_triangle(point, vertices_[0], vertices_[1], vertices_[2]); + return barycentric_.is_point_inside_triangle(point); } bool is_edge_inside_cursor(const TriangleSelector::Triangle& tr, const std::vector& vertices) const override @@ -2305,19 +2350,19 @@ public: } // If any projected end is inside the triangle, then is in - if (is_point_inside_triangle(pt_a, vertices_[0], vertices_[1], vertices_[2]) || - is_point_inside_triangle(pt_b, vertices_[0], vertices_[1], vertices_[2])) { + if (barycentric_.is_point_inside_triangle(pt_a) || + barycentric_.is_point_inside_triangle(pt_b)) { return true; } // Otherwise see if the segment (pt_a, pt_b) intersects the triangle { - const Vec3f uvw_a = barycentric(pt_a, vertices_[0], vertices_[1], vertices_[2]); - const Vec3f uvw_b = barycentric(pt_b, vertices_[0], vertices_[1], vertices_[2]); - auto proj = [](const Vec3f& p) -> std::pair { return {p(0), p(1)}; }; - const Vec3f uvw_0 = {1.f,0.f,0.f}; - const Vec3f uvw_1 = {0.f,1.f,0.f}; - const Vec3f uvw_2 = {0.f,0.f,1.f}; + const Vec3f uvw_a = barycentric_.calc(pt_a); + const Vec3f uvw_b = barycentric_.calc(pt_b); + const Vec3f uvw_0 {1.f,0.f,0.f}; + const Vec3f uvw_1 {0.f,1.f,0.f}; + const Vec3f uvw_2 {0.f,0.f,1.f}; + constexpr std::pair proj{0, 1}; if (segments_intersect_proj(uvw_a, uvw_b, uvw_0, uvw_1, proj)|| segments_intersect_proj(uvw_a, uvw_b, uvw_0, uvw_2, proj)|| @@ -2343,7 +2388,7 @@ public: } private: - const std::array vertices_; + const Barycentric barycentric_; const float tolerance_ = EPSILON; const float tolerance_sqr_ = tolerance_ * tolerance_; static const double facet_angle_limit; @@ -2413,12 +2458,9 @@ TriangleSelector::TriangleSplittingData TriangleSelector::remap_painting( Vec3f n_abs = (n1.cwiseAbs() + n2.cwiseAbs()); int axis = (n_abs.x() >= n_abs.y() && n_abs.x() >= n_abs.z()) ? 0 : (n_abs.y() >= n_abs.z()) ? 1 : 2; - int u_axis = (axis + 1) % 3; - int v_axis = (axis + 2) % 3; - - auto proj = [&](const Vec3f& p) -> std::pair { - return { p(u_axis), p(v_axis) }; - }; + const int u_axis = (axis + 1) % 3; + const int v_axis = (axis + 2) % 3; + const std::pair proj{u_axis, v_axis}; if (segments_intersect_proj(pa, pb, ta, tb, proj)) return true; if (segments_intersect_proj(pa, pb, tb, tc, proj)) return true; From 83e9f17aa817c28c03ea68db610aca59feebca44 Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Thu, 7 May 2026 22:24:45 +0800 Subject: [PATCH 010/117] Fix issue that `Geometry::deg2rad()` do calculation in the same type as the parameter, which means if the parameter is `int` then you lose all the precision --- src/libslic3r/Geometry.hpp | 6 +++++- src/libslic3r/TriangleSelector.cpp | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 0ae7524fa0..e610a9ed4f 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -298,7 +298,11 @@ bool directions_perpendicular(double angle1, double angle2, double max_diff = 0) template bool contains(const std::vector &vector, const Point &point); template T rad2deg(T angle) { return T(180.0) * angle / T(PI); } double rad2deg_dir(double angle); -template constexpr T deg2rad(const T angle) { return T(PI) * angle / T(180.0); } +template constexpr T deg2rad(const T angle) +{ + static_assert(std::is_floating_point::value, "Why do you want to calculate angle in integer?"); + return T(PI) * angle / T(180.0); +} template T angle_to_0_2PI(T angle) { static const T TWO_PI = T(2) * T(PI); diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index e4d1a4521c..19f638a160 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -2399,7 +2399,7 @@ private: return line.distance(p); } }; -const double TriangleCursor::facet_angle_limit = cos(Geometry::deg2rad(5)); +const double TriangleCursor::facet_angle_limit = cos(Geometry::deg2rad(5.0)); // Remap painting data from source mesh to target mesh using spatial mapping. From fad680fcc160034f0aaa03d52e1a2366529a731e Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Fri, 8 May 2026 21:23:38 +0800 Subject: [PATCH 011/117] Simplify mesh transform --- src/libslic3r/CutUtils.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libslic3r/CutUtils.cpp b/src/libslic3r/CutUtils.cpp index 16f0a19816..a2cd7583a5 100644 --- a/src/libslic3r/CutUtils.cpp +++ b/src/libslic3r/CutUtils.cpp @@ -368,8 +368,7 @@ const ModelObjectPtrs& Cut::perform_with_plane() // Get mesh in cut space (same transform as process_volume_cut applies) TriangleMesh mesh(volume->mesh()); const auto volume_matrix = volume->get_matrix(); - mesh.transform(inverse_cut_matrix * instance_matrix * volume_matrix, true); - mesh.transform(m_cut_matrix); + mesh.transform(instance_matrix * volume_matrix, true); SavedPainting sp; sp.its = std::move(mesh.its); sp.supported = volume->supported_facets.get_data(); From 2f160ad8bc397431d1146ecbec757130de2c90dd Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Fri, 8 May 2026 22:54:20 +0800 Subject: [PATCH 012/117] Rearrange code a little bit to make it more reusable --- src/libslic3r/CutUtils.cpp | 40 +++++++++--------------------- src/libslic3r/Model.hpp | 18 ++++++++++++++ src/libslic3r/TriangleSelector.hpp | 9 +++++++ 3 files changed, 39 insertions(+), 28 deletions(-) diff --git a/src/libslic3r/CutUtils.cpp b/src/libslic3r/CutUtils.cpp index a2cd7583a5..8586b952f3 100644 --- a/src/libslic3r/CutUtils.cpp +++ b/src/libslic3r/CutUtils.cpp @@ -14,17 +14,8 @@ namespace Slic3r { using namespace Geometry; -// Saved painting data for remapping after mesh change. -struct SavedPainting { - indexed_triangle_set its; // Original mesh - TriangleSelector::TriangleSplittingData supported; - TriangleSelector::TriangleSplittingData seam; - TriangleSelector::TriangleSplittingData mmu; - TriangleSelector::TriangleSplittingData fuzzy; -}; - // Remap painting data from saved source to a cut result mesh, and set on a volume. -static void remap_and_set_painting(ModelVolume* vol, const SavedPainting* saved) +static void remap_and_set_painting(ModelVolume* vol, const std::optional& saved) { if (!saved) { return; @@ -34,7 +25,7 @@ static void remap_and_set_painting(ModelVolume* vol, const SavedPainting* saved) FacetsAnnotation& target_facets) { if (src_data.bitstream.empty()) return; - auto result = TriangleSelector::remap_painting(saved->its, src_data, vol->mesh().its, translation_transform(vol->mesh().get_init_shift())); + auto result = TriangleSelector::remap_painting(saved->mesh.its, src_data, vol->mesh().its, translation_transform(vol->mesh().get_init_shift())); if (!result.bitstream.empty()) target_facets.set_data(result); }; @@ -211,7 +202,7 @@ static void process_modifier_cut(ModelVolume* volume, const Transform3d& instanc static void process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, - const SavedPainting* saved_painting = nullptr) + const std::optional& saved_painting) { // Perform cut TriangleMesh upper_mesh, lower_mesh; @@ -362,20 +353,14 @@ const ModelObjectPtrs& Cut::perform_with_plane() for (ModelVolume* volume : mo->volumes) { // Save painting data before reset_extra_facets() discards it. - // Only for model parts that will be cut (not modifiers/connectors). - std::optional saved_painting; - if (m_attributes.has(ModelObjectCutAttribute::KeepPaint) && volume->is_any_painted() && volume->is_model_part() && !volume->mesh().empty()) { - // Get mesh in cut space (same transform as process_volume_cut applies) - TriangleMesh mesh(volume->mesh()); - const auto volume_matrix = volume->get_matrix(); - mesh.transform(instance_matrix * volume_matrix, true); - SavedPainting sp; - sp.its = std::move(mesh.its); - sp.supported = volume->supported_facets.get_data(); - sp.seam = volume->seam_facets.get_data(); - sp.mmu = volume->mmu_segmentation_facets.get_data(); - sp.fuzzy = volume->fuzzy_skin_facets.get_data(); - saved_painting = std::move(sp); + std::optional saved_painting; + if (m_attributes.has(ModelObjectCutAttribute::KeepPaint)) { + saved_painting = volume->save_painting(); + if (saved_painting) { + // Transform mesh to cut space (same transform as process_volume_cut applies) + const auto volume_matrix = volume->get_matrix(); + saved_painting->mesh.transform(instance_matrix * volume_matrix, true); + } } volume->reset_extra_facets(); @@ -387,8 +372,7 @@ const ModelObjectPtrs& Cut::perform_with_plane() process_connector_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower, dowels); } else if (!volume->mesh().empty()) - process_solid_part_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower, - saved_painting ? &*saved_painting : nullptr); + process_solid_part_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower, saved_painting); } // Post-process cut parts diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 0de76dbef4..ccc0760097 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -878,6 +878,24 @@ public: // List of mesh facets painted for fuzzy skin. FacetsAnnotation fuzzy_skin_facets; + // Save painting data before reset_extra_facets() discards it. + // Used for replacing mesh without losing painting data. + // Only for model parts (not modifiers/connectors). + std::optional save_painting() const + { + if (is_any_painted() && is_model_part() && !mesh().empty()) { + TriangleSelector::SavedPainting sp; + sp.mesh = mesh(); + sp.supported = supported_facets.get_data(); + sp.seam = seam_facets.get_data(); + sp.mmu = mmu_segmentation_facets.get_data(); + sp.fuzzy = fuzzy_skin_facets.get_data(); + return sp; + } + + return {}; + } + // BBS: quick access for volume extruders, 1 based mutable std::vector mmuseg_extruders; mutable Timestamp mmuseg_ts; diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index 8f02c7d717..412c9fb9b1 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -372,6 +372,15 @@ public: // The operation may merge split triangles if they are being assigned the same color. void seed_fill_apply_on_triangles(EnforcerBlockerType new_state); + // Saved painting data for remapping after mesh change. + struct SavedPainting { + TriangleMesh mesh; // Original mesh + TriangleSplittingData supported; + TriangleSplittingData seam; + TriangleSplittingData mmu; + TriangleSplittingData fuzzy; + }; + // Remap painting data from source mesh to target mesh using spatial mapping. // `target_transform` should transform the target mesh into source's coordinate space. static TriangleSplittingData remap_painting( From 4490dccade7deb179a9b763dc748209fb450be72 Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Sat, 9 May 2026 10:12:36 +0800 Subject: [PATCH 013/117] Fix issue that support/seam/fuzzy skin painting not kept after split --- src/libslic3r/Model.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index ef5ef6dbe6..bcdf1e54d3 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -2078,10 +2078,16 @@ void ModelObject::split(ModelObjectPtrs* new_objects) ModelVolume* new_vol = new_object->add_volume(*volume, std::move(mesh)); if (is_multi_volume_object) { - // BBS: volume geometry not changed, so we can keep the color paint facets - if (new_vol->mmu_segmentation_facets.timestamp() == volume->mmu_segmentation_facets.timestamp()) - new_vol->mmu_segmentation_facets.reset(); // BBS: let next assign take effect - new_vol->mmu_segmentation_facets.assign(volume->mmu_segmentation_facets); + // BBS: volume geometry not changed, so we can keep the paint facets +#define COPY_FACETS(f) \ + if (new_vol->f.timestamp() == volume->f.timestamp()) \ + new_vol->f.reset(); /* BBS: let next assign take effect */ \ + new_vol->f.assign(volume->f) + + COPY_FACETS(supported_facets); + COPY_FACETS(seam_facets); + COPY_FACETS(mmu_segmentation_facets); + COPY_FACETS(fuzzy_skin_facets); } // BBS: clear volume's config, as we already set them into object From bfde91685e420d1f29801c39be29bbbabe2eb7cd Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Sat, 9 May 2026 16:16:06 +0800 Subject: [PATCH 014/117] Rearrange code a little bit to make it more reusable --- src/libslic3r/CutUtils.cpp | 29 ++++------------------------- src/libslic3r/Model.cpp | 35 +++++++++++++++++++++++++++++++++++ src/libslic3r/Model.hpp | 18 ++++-------------- 3 files changed, 43 insertions(+), 39 deletions(-) diff --git a/src/libslic3r/CutUtils.cpp b/src/libslic3r/CutUtils.cpp index 8586b952f3..7fc21bb3b4 100644 --- a/src/libslic3r/CutUtils.cpp +++ b/src/libslic3r/CutUtils.cpp @@ -14,27 +14,6 @@ namespace Slic3r { using namespace Geometry; -// Remap painting data from saved source to a cut result mesh, and set on a volume. -static void remap_and_set_painting(ModelVolume* vol, const std::optional& saved) -{ - if (!saved) { - return; - } - - auto remap_one = [&](const TriangleSelector::TriangleSplittingData& src_data, - FacetsAnnotation& target_facets) { - if (src_data.bitstream.empty()) - return; - auto result = TriangleSelector::remap_painting(saved->mesh.its, src_data, vol->mesh().its, translation_transform(vol->mesh().get_init_shift())); - if (!result.bitstream.empty()) - target_facets.set_data(result); - }; - remap_one(saved->supported, vol->supported_facets); - remap_one(saved->seam, vol->seam_facets); - remap_one(saved->mmu, vol->mmu_segmentation_facets); - remap_one(saved->fuzzy, vol->fuzzy_skin_facets); -} - static void apply_tolerance(ModelVolume* vol) { ModelVolume::CutInfo& cut_info = vol->cut_info; @@ -213,24 +192,24 @@ static void process_solid_part_cut(ModelVolume* volume, const Transform3d& insta if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) { if (!upper_mesh.empty()) { add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A"); - remap_and_set_painting(upper->volumes.back(), saved_painting); + upper->volumes.back()->restore_painting(saved_painting); } if (!lower_mesh.empty()) { add_cut_volume(lower_mesh, upper, volume, cut_matrix, "_B"); upper->volumes.back()->cut_info.is_from_upper = false; - remap_and_set_painting(upper->volumes.back(), saved_painting); + upper->volumes.back()->restore_painting(saved_painting); } return; } if (attributes.has(ModelObjectCutAttribute::KeepUpper) && !upper_mesh.empty()) { add_cut_volume(upper_mesh, upper, volume, cut_matrix); - remap_and_set_painting(upper->volumes.back(), saved_painting); + upper->volumes.back()->restore_painting(saved_painting); } if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty()) { add_cut_volume(lower_mesh, lower, volume, cut_matrix); - remap_and_set_painting(lower->volumes.back(), saved_painting); + lower->volumes.back()->restore_painting(saved_painting); } } diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index bcdf1e54d3..57a114479b 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1980,6 +1980,41 @@ void ModelVolume::reset_extra_facets() this->fuzzy_skin_facets.reset(); } +std::optional ModelVolume::save_painting() const +{ + if (is_any_painted() && is_model_part() && !mesh().empty()) { + TriangleSelector::SavedPainting sp; + sp.mesh = mesh(); + sp.supported = supported_facets.get_data(); + sp.seam = seam_facets.get_data(); + sp.mmu = mmu_segmentation_facets.get_data(); + sp.fuzzy = fuzzy_skin_facets.get_data(); + return sp; + } + + return {}; +} + +void ModelVolume::restore_painting(const std::optional& saved) +{ + if (!saved) { + return; + } + + auto remap_one = [&](const TriangleSelector::TriangleSplittingData& src_data, + FacetsAnnotation& target_facets) { + if (src_data.bitstream.empty()) + return; + auto result = TriangleSelector::remap_painting(saved->mesh.its, src_data, mesh().its, Geometry::translation_transform(mesh().get_init_shift())); + if (!result.bitstream.empty()) + target_facets.set_data(result); + }; + remap_one(saved->supported, supported_facets); + remap_one(saved->seam, seam_facets); + remap_one(saved->mmu, mmu_segmentation_facets); + remap_one(saved->fuzzy, fuzzy_skin_facets); +} + static void invalidate_translations(ModelObject* object, const ModelInstance* src_instance) { if (!object->origin_translation.isApprox(Vec3d::Zero()) && src_instance->get_offset().isApprox(Vec3d::Zero())) { diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index ccc0760097..5bf345607d 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -881,20 +881,10 @@ public: // Save painting data before reset_extra_facets() discards it. // Used for replacing mesh without losing painting data. // Only for model parts (not modifiers/connectors). - std::optional save_painting() const - { - if (is_any_painted() && is_model_part() && !mesh().empty()) { - TriangleSelector::SavedPainting sp; - sp.mesh = mesh(); - sp.supported = supported_facets.get_data(); - sp.seam = seam_facets.get_data(); - sp.mmu = mmu_segmentation_facets.get_data(); - sp.fuzzy = fuzzy_skin_facets.get_data(); - return sp; - } - - return {}; - } + std::optional save_painting() const; + + // Remap painting data from previous saved source to this mesh + void restore_painting(const std::optional& saved); // BBS: quick access for volume extruders, 1 based mutable std::vector mmuseg_extruders; From c2d33bea08d1d6e51451d51248382b0a98294257 Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Sat, 9 May 2026 16:18:49 +0800 Subject: [PATCH 015/117] Remap paint after split to objects --- src/OrcaSlicer.cpp | 2 +- src/libslic3r/Model.cpp | 10 +++++++++- src/libslic3r/Model.hpp | 2 +- src/slic3r/GUI/Plater.cpp | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/OrcaSlicer.cpp b/src/OrcaSlicer.cpp index ba52f6787b..b258e096d4 100644 --- a/src/OrcaSlicer.cpp +++ b/src/OrcaSlicer.cpp @@ -4450,7 +4450,7 @@ int CLI::run(int argc, char **argv) size_t num_objects = model.objects.size(); for (size_t i = 0; i < num_objects; ++ i) { ModelObjectPtrs new_objects; - model.objects.front()->split(&new_objects); + model.objects.front()->split(&new_objects, false); // TODO: add cli option to enable this? model.delete_object(size_t(0)); } } diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 57a114479b..09751cd7df 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -2028,13 +2028,14 @@ static void invalidate_translations(ModelObject* object, const ModelInstance* sr } } -void ModelObject::split(ModelObjectPtrs* new_objects) +void ModelObject::split(ModelObjectPtrs* new_objects, const bool remap_paint) { std::vector all_meshes; std::vector all_transfos; std::vector> volume_mesh_counts; all_meshes.reserve(this->volumes.size() * 5); bool is_multi_volume_object = (this->volumes.size() > 1); + std::optional saved_painting; for (int volume_idx = 0; volume_idx < this->volumes.size(); volume_idx++) { ModelVolume* volume = this->volumes[volume_idx]; @@ -2046,6 +2047,10 @@ void ModelObject::split(ModelObjectPtrs* new_objects) volume->text_configuration.reset(); if (!is_multi_volume_object) { + if (remap_paint) { + // Save painting so we could restore them after the mesh split + saved_painting = volume->save_painting(); + } //BBS: not multi volume object, then split mesh. std::vector volume_meshes = volume->mesh().split(); int mesh_count = 0; @@ -2123,6 +2128,9 @@ void ModelObject::split(ModelObjectPtrs* new_objects) COPY_FACETS(seam_facets); COPY_FACETS(mmu_segmentation_facets); COPY_FACETS(fuzzy_skin_facets); + } else if (saved_painting) { + // Geometry changed, attempt to remap them to the new mesh + new_vol->restore_painting(saved_painting); } // BBS: clear volume's config, as we already set them into object diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 5bf345607d..6a672defb0 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -516,7 +516,7 @@ public: void delete_connectors(); void clone_for_cut(ModelObject **obj); - void split(ModelObjectPtrs*new_objects); + void split(ModelObjectPtrs*new_objects, bool remap_paint); void merge(); // BBS: Boolean opts - Musang King diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index ec74a796e4..f78cc3f9b7 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -7603,7 +7603,7 @@ void Plater::priv::split_object(int obj_idx, bool auto_drop /* = true */) wxBusyCursor wait; ModelObjectPtrs new_objects; - current_model_object->split(&new_objects); + current_model_object->split(&new_objects, wxGetApp().app_config->get_bool("keep_painting")); if (new_objects.size() == 1) // #ysFIXME use notification Slic3r::GUI::warning_catcher(q, _L("The selected object couldn't be split.")); From fb0cf966cf5bd1ba77f6396204931d2a44b5718e Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Sat, 9 May 2026 17:06:19 +0800 Subject: [PATCH 016/117] Remap paint after split to parts --- src/libslic3r/Model.cpp | 7 ++++++- src/libslic3r/Model.hpp | 2 +- src/slic3r/GUI/GUI_ObjectList.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 2 +- src/slic3r/Utils/FixModelByCgal.cpp | 2 +- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 09751cd7df..586043bf4b 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -2731,7 +2731,7 @@ std::string ModelVolume::type_to_string(const ModelVolumeType t) // Split this volume, append the result to the object owning this volume. // Return the number of volumes created from this one. // This is useful to assign different materials to different volumes of an object. -size_t ModelVolume::split(unsigned int max_extruders) +size_t ModelVolume::split(unsigned int max_extruders, bool remap_paint) { std::vector meshes = this->mesh().split(); if (meshes.size() <= 1) @@ -2741,6 +2741,9 @@ size_t ModelVolume::split(unsigned int max_extruders) if (text_configuration.has_value()) text_configuration.reset(); + std::optional saved_painting = remap_paint ? save_painting() : + std::optional{}; + size_t idx = 0; size_t ivolume = std::find(this->object->volumes.begin(), this->object->volumes.end(), this) - this->object->volumes.begin(); const std::string name = this->name; @@ -2776,6 +2779,8 @@ size_t ModelVolume::split(unsigned int max_extruders) this->object->volumes[ivolume]->config.set("extruder", this->extruder_id()); //this->object->volumes[ivolume]->config.set("extruder", auto_extruder_id(max_extruders, extruder_counter)); this->object->volumes[ivolume]->m_is_splittable = 0; + this->object->volumes[ivolume]->restore_painting(saved_painting); + ++ idx; } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 6a672defb0..fbe8da7b6a 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -930,7 +930,7 @@ public: // Split this volume, append the result to the object owning this volume. // Return the number of volumes created from this one. // This is useful to assign different materials to different volumes of an object. - size_t split(unsigned int max_extruders); + size_t split(unsigned int max_extruders, bool remap_paint); void translate(double x, double y, double z) { translate(Vec3d(x, y, z)); } void translate(const Vec3d& displacement); void scale(const Vec3d& scaling_factors); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 726ea1aca3..81a4791a0d 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2849,7 +2849,7 @@ void ObjectList::split() take_snapshot("Split to parts"); - volume->split(filament_cnt); + volume->split(filament_cnt, wxGetApp().app_config->get_bool("keep_painting")); wxBusyCursor wait; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index bfc757589c..c2e4a19a73 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -1908,7 +1908,7 @@ GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* mo, const Transfor // split to parts for (int id = int(volumes.size())-1; id >= 0; id--) if (volumes[id]->is_splittable()) - volumes[id]->split(1); + volumes[id]->split(1, false); // TODO: fix this m_parts.clear(); for (const ModelVolume* volume : volumes) { diff --git a/src/slic3r/Utils/FixModelByCgal.cpp b/src/slic3r/Utils/FixModelByCgal.cpp index c014ceaf04..e1f4320fec 100644 --- a/src/slic3r/Utils/FixModelByCgal.cpp +++ b/src/slic3r/Utils/FixModelByCgal.cpp @@ -112,7 +112,7 @@ bool fix_model_with_cgal_gui(ModelObject &model_object, int volume_idx, GUI::Pro // Orca: Split splittable volumes into parts for individual processing. size_t parts_count = 1; if (volume->is_splittable()) { - parts_count = volume->split(1); + parts_count = volume->split(1, false); // TODO: fix this if (parts_count > 1) { const std::string msg = Slic3r::format(L("Split into %1% parts"), parts_count); on_progress(msg.c_str(), 10); From b3a513eab9a014e01def5c80e3958dc3fea0eaea Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Sat, 9 May 2026 17:36:17 +0800 Subject: [PATCH 017/117] Remove redundent mmu facets assign because that's already done in `new_object->add_volume(*volume)` --- src/slic3r/GUI/GUI_ObjectList.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 81a4791a0d..0a8d0f480a 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -3041,7 +3041,6 @@ void ObjectList::merge(bool to_multipart_object) auto opt = object->config.option("extruder"); if (opt) { new_volume->config.set_key_value("extruder", new ConfigOptionInt(opt->getInt())); } } - new_volume->mmu_segmentation_facets.assign(std::move(volume->mmu_segmentation_facets)); } new_object->sort_volumes(true); From e6b3f6ccef662428a3c909c4f3769111333067f3 Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Sat, 9 May 2026 21:47:40 +0800 Subject: [PATCH 018/117] Add option to keep existing painting while mapping new paintings to it --- src/libslic3r/Model.cpp | 10 +++++++--- src/libslic3r/Model.hpp | 4 ++-- src/libslic3r/TriangleSelector.cpp | 7 ++++++- src/libslic3r/TriangleSelector.hpp | 4 +++- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 586043bf4b..000ab997a9 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1995,7 +1995,7 @@ std::optional ModelVolume::save_painting() cons return {}; } -void ModelVolume::restore_painting(const std::optional& saved) +void ModelVolume::restore_painting(const std::optional& saved, const bool keep_existing_paint) { if (!saved) { return; @@ -2005,9 +2005,13 @@ void ModelVolume::restore_painting(const std::optionalmesh.its, src_data, mesh().its, Geometry::translation_transform(mesh().get_init_shift())); + auto result = + TriangleSelector::remap_painting(saved->mesh.its, src_data, mesh().its, Geometry::translation_transform(mesh().get_init_shift()), + keep_existing_paint ? + std::optional>{std::ref(target_facets.get_data())} : + std::optional>{}); if (!result.bitstream.empty()) - target_facets.set_data(result); + target_facets.set_data(std::move(result)); }; remap_one(saved->supported, supported_facets); remap_one(saved->seam, seam_facets); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index fbe8da7b6a..d8697adb41 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -731,7 +731,7 @@ public: void assign(const FacetsAnnotation &rhs) { if (! this->timestamp_matches(rhs)) { m_data = rhs.m_data; this->copy_timestamp(rhs); } } void assign(FacetsAnnotation &&rhs) { if (! this->timestamp_matches(rhs)) { m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } } const TriangleSelector::TriangleSplittingData &get_data() const noexcept { return m_data; } - void set_data(const TriangleSelector::TriangleSplittingData &data) { m_data = data; this->touch(); } + void set_data(TriangleSelector::TriangleSplittingData &&data) { m_data = std::move(data); this->touch(); } bool set(const TriangleSelector& selector); indexed_triangle_set get_facets(const ModelVolume& mv, EnforcerBlockerType type) const; // BBS @@ -884,7 +884,7 @@ public: std::optional save_painting() const; // Remap painting data from previous saved source to this mesh - void restore_painting(const std::optional& saved); + void restore_painting(const std::optional& saved, bool keep_existing_paint = false); // BBS: quick access for volume extruders, 1 based mutable std::vector mmuseg_extruders; diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 19f638a160..0fb1423df4 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -2407,7 +2407,8 @@ TriangleSelector::TriangleSplittingData TriangleSelector::remap_painting( const indexed_triangle_set& source_its, const TriangleSplittingData& source_painting, const indexed_triangle_set& target_its, - const Transform3d& target_transform) + const Transform3d& target_transform, + const std::optional>& existing_painting) { TriangleSelector::TriangleSplittingData result; if (source_painting.bitstream.empty()) @@ -2477,6 +2478,10 @@ TriangleSelector::TriangleSplittingData TriangleSelector::remap_painting( // 4. For each painted face, we find the nearest target face, and apply the TriangleCursor to paint it TriangleSelector target_selector(target_mesh); + if (existing_painting) { + // Restore existing painting first, if given + target_selector.deserialize(existing_painting->get(), false); + } for (auto tri_ref : painted_triangles) { const Triangle& tri = tri_ref.get(); const Vec3f& pv0 = source_selector.m_vertices[tri.verts_idxs[0]].v; diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index 412c9fb9b1..09b606b5d2 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -383,11 +383,13 @@ public: // Remap painting data from source mesh to target mesh using spatial mapping. // `target_transform` should transform the target mesh into source's coordinate space. + // If `existing_painting` is present, the result will be a combine of `existing_painting` and remapped `source_painting`. static TriangleSplittingData remap_painting( const indexed_triangle_set& source_its, const TriangleSplittingData& source_painting, const indexed_triangle_set& target_its, - const Transform3d& target_transform); + const Transform3d& target_transform, + const std::optional>& existing_painting); protected: // Triangle and info about how it's split. From 3aadd6e0808e7aed0011f45d4c04b3230f4ef1d7 Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Sat, 9 May 2026 21:48:52 +0800 Subject: [PATCH 019/117] Remap paint after mesh boolean --- src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.cpp | 32 +++++++++++++++++--- src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp | 4 ++- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.cpp index bfff70d315..c7186e5d80 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.cpp @@ -188,6 +188,19 @@ CommonGizmosDataID GLGizmoMeshBoolean::on_get_requirements() const | int(CommonGizmosDataID::ObjectClipper)); } +std::optional VolumeInfo::save_painting() const +{ + if (wxGetApp().app_config->get_bool("keep_painting")) { + std::optional saved_painting = mv->save_painting(); + if (saved_painting) { + saved_painting->mesh.transform(trafo); + } + return saved_painting; + } + + return {}; +} + void GLGizmoMeshBoolean::on_render_input_window(float x, float y, float bottom_limit) { y = std::min(y, bottom_limit - ImGui::GetWindowHeight()); @@ -346,7 +359,9 @@ void GLGizmoMeshBoolean::on_render_input_window(float x, float y, float bottom_l std::vector temp_mesh_resuls; Slic3r::MeshBoolean::mcut::make_boolean(temp_src_mesh, temp_tool_mesh, temp_mesh_resuls, "UNION"); if (temp_mesh_resuls.size() != 0) { - generate_new_volume(true, *temp_mesh_resuls.begin()); + // For union, we want to keep paint from both meshes + std::vector> saved_paintings{m_src.save_painting(), m_tool.save_painting()}; + generate_new_volume(true, *temp_mesh_resuls.begin(), saved_paintings); wxGetApp().notification_manager()->close_plater_warning_notification(warning_text); } else { @@ -364,7 +379,9 @@ void GLGizmoMeshBoolean::on_render_input_window(float x, float y, float bottom_l std::vector temp_mesh_resuls; Slic3r::MeshBoolean::mcut::make_boolean(temp_src_mesh, temp_tool_mesh, temp_mesh_resuls, "A_NOT_B"); if (temp_mesh_resuls.size() != 0) { - generate_new_volume(m_diff_delete_input, *temp_mesh_resuls.begin()); + // For diff, we only need paint from src + std::vector> saved_paintings{m_src.save_painting()}; + generate_new_volume(m_diff_delete_input, *temp_mesh_resuls.begin(), saved_paintings); wxGetApp().notification_manager()->close_plater_warning_notification(warning_text); } else { @@ -382,7 +399,9 @@ void GLGizmoMeshBoolean::on_render_input_window(float x, float y, float bottom_l std::vector temp_mesh_resuls; Slic3r::MeshBoolean::mcut::make_boolean(temp_src_mesh, temp_tool_mesh, temp_mesh_resuls, "INTERSECTION"); if (temp_mesh_resuls.size() != 0) { - generate_new_volume(m_inter_delete_input, *temp_mesh_resuls.begin()); + // For intersection, we want to keep paint from both meshes + std::vector> saved_paintings{m_src.save_painting(), m_tool.save_painting()}; + generate_new_volume(m_inter_delete_input, *temp_mesh_resuls.begin(), saved_paintings); wxGetApp().notification_manager()->close_plater_warning_notification(warning_text); } else { @@ -420,7 +439,7 @@ void GLGizmoMeshBoolean::on_save(cereal::BinaryOutputArchive &ar) const ar(m_enable, m_operation_mode, m_selecting_state, m_diff_delete_input, m_inter_delete_input, m_src, m_tool); } -void GLGizmoMeshBoolean::generate_new_volume(bool delete_input, const TriangleMesh& mesh_result) { +void GLGizmoMeshBoolean::generate_new_volume(const bool delete_input, TriangleMesh& mesh_result, const std::vector>& saved_paintings) { wxGetApp().plater()->take_snapshot("Mesh Boolean"); @@ -429,6 +448,11 @@ void GLGizmoMeshBoolean::generate_new_volume(bool delete_input, const TriangleMe // generate new volume ModelVolume* new_volume = curr_model_object->add_volume(std::move(mesh_result)); + // Remap paintings + for (const auto& saved_painting : saved_paintings) { + new_volume->restore_painting(saved_painting, true); + } + // assign to new_volume from old_volume ModelVolume* old_volume = m_src.mv; std::string suffix; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp index c012a68dca..9c36be5cd9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp @@ -34,6 +34,8 @@ struct VolumeInfo { void serialize(Archive& ar) { ar(volume_idx, trafo); } + + std::optional save_painting() const; }; class GLGizmoMeshBoolean : public GLGizmoBase { @@ -87,7 +89,7 @@ private: VolumeInfo m_src; VolumeInfo m_tool; - void generate_new_volume(bool delete_input, const TriangleMesh& mesh_result); + void generate_new_volume(bool delete_input, TriangleMesh& mesh_result, const std::vector>& saved_paintings); }; } // namespace GUI From f8a18e7656f2146b56cc3ae78d48690da7d88ad1 Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Sun, 10 May 2026 00:41:18 +0800 Subject: [PATCH 020/117] Remap paint after mesh boolean (from context menu) --- src/slic3r/GUI/GUI_ObjectList.cpp | 39 +++++++++++++++++++++++++++++++ src/slic3r/GUI/Plater.cpp | 2 +- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 0a8d0f480a..9da5edf6c3 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -3225,6 +3225,22 @@ void ObjectList::boolean() Plater::TakeSnapshot snapshot(wxGetApp().plater(), "boolean"); ModelObject* object = (*m_objects)[obj_idxs.front()]; + + const bool keep_painting = wxGetApp().app_config->get_bool("keep_painting"); + std::vector> saved_paintings; + if (keep_painting) { + // Save painting of all the positive parts + saved_paintings.reserve(object->volumes.size()); + for (const ModelVolume* vol : object->volumes) { + if (vol && vol->mesh_ptr() && vol->is_model_part() && vol->is_any_painted()) { + saved_paintings.emplace_back(vol->save_painting()); + if (saved_paintings.back()) { + saved_paintings.back()->mesh.transform(vol->get_matrix(), true); + } + } + } + } + TriangleMesh mesh = Plater::combine_mesh_fff(*object, -1, [this](const std::string& msg) {return wxGetApp().notification_manager()->push_plater_error_notification(msg); }); // add mesh to model as a new object, keep the original object's name and config @@ -3236,6 +3252,29 @@ void ObjectList::boolean() new_object->add_instance(); ModelVolume* new_volume = new_object->add_volume(mesh); + // Remap paint + if (keep_painting) { + for (auto& saved_painting : saved_paintings) { + if (saved_painting) { + // For each original painted volume, we need to apply to each instance + // because we merged all instances into one in `combine_mesh_fff` + + // First we save the non-instance-translated mesh + TriangleMesh vols_mesh(std::move(saved_painting->mesh)); + + for (const ModelInstance* i : object->instances) { + // Then for each instance, we apply the paint at the given instance place + saved_painting->mesh = vols_mesh; + saved_painting->mesh.transform(i->get_matrix()); + + // Then paint it + new_volume->restore_painting(saved_painting, true); + } + + } + } + } + // BBS: ensure on bed but no need to ensure locate in the center around origin new_object->ensure_on_bed(); new_object->center_around_origin(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index f78cc3f9b7..e95d428248 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -15108,7 +15108,7 @@ TriangleMesh Plater::combine_mesh_fff(const ModelObject& mo, int instance_id, st } if (instance_id == -1) { - TriangleMesh vols_mesh(mesh); + TriangleMesh vols_mesh(std::move(mesh)); mesh = TriangleMesh(); for (const ModelInstance* i : mo.instances) { TriangleMesh m = vols_mesh; From 219ef664bd0b7e53f280410410f97562c0d15226 Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Sun, 10 May 2026 11:45:15 +0800 Subject: [PATCH 021/117] Fix issue that in certain cases the paint is not applied when faces are parallel to an axis, by adding a small tolerance to the bbox --- src/libslic3r/TriangleSelector.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 0fb1423df4..93cb8747a6 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -2335,18 +2335,18 @@ public: // If both ends at the same side and farther than tolerance, skip const float dist_a = pts_dist[idx_a]; const float dist_b = pts_dist[idx_b]; - if ((dist_a > tolerance_ && dist_b > tolerance_) || (dist_a < -tolerance_ && dist_b < -tolerance_)) { + if ((dist_a > tolerance && dist_b > tolerance) || (dist_a < -tolerance && dist_b < -tolerance)) { continue; } // Find the projected line segment which has distance to the plane within the tolerance Vec3f pt_a = pts_proj[idx_a]; Vec3f pt_b = pts_proj[idx_b]; - if (std::abs(dist_a) > tolerance_) { - pt_a = (tolerance_ - dist_b) / (dist_a - dist_b) * (pts_proj[idx_a] - pts_proj[idx_b]) + pts_proj[idx_b]; + if (std::abs(dist_a) > tolerance) { + pt_a = (tolerance - dist_b) / (dist_a - dist_b) * (pts_proj[idx_a] - pts_proj[idx_b]) + pts_proj[idx_b]; } - if (std::abs(dist_b) > tolerance_) { - pt_b = (tolerance_ - dist_a) / (dist_b - dist_a) * (pts_proj[idx_b] - pts_proj[idx_a]) + pts_proj[idx_a]; + if (std::abs(dist_b) > tolerance) { + pt_b = (tolerance - dist_a) / (dist_b - dist_a) * (pts_proj[idx_b] - pts_proj[idx_a]) + pts_proj[idx_a]; } // If any projected end is inside the triangle, then is in @@ -2387,10 +2387,10 @@ public: return std::clamp(a, 0.f, 1.f) >= facet_angle_limit; } + static constexpr float tolerance = 0.01f; + private: const Barycentric barycentric_; - const float tolerance_ = EPSILON; - const float tolerance_sqr_ = tolerance_ * tolerance_; static const double facet_angle_limit; static float point_to_line_dist(const Vec3f& p, const Vec3f& a, const Vec3f& b) @@ -2494,6 +2494,8 @@ TriangleSelector::TriangleSplittingData TriangleSelector::remap_painting( pt_bbox.extend(pv0); pt_bbox.extend(pv1); pt_bbox.extend(pv2); + pt_bbox.min() -= Eigen::Vector3f::Constant(TriangleCursor::tolerance); + pt_bbox.max() += Eigen::Vector3f::Constant(TriangleCursor::tolerance); AABBTreeIndirect::traverse(target_tree, AABBTreeIndirect::intersecting(pt_bbox), [&](const AABBTreeIndirect::Tree3f::Node& node) -> bool { size_t face_idx = node.idx; From 73096a11611789ab8a4c092fdcc55520747a64c5 Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Sun, 10 May 2026 12:35:31 +0800 Subject: [PATCH 022/117] Fix artifact during normal paint --- src/libslic3r/TriangleSelector.cpp | 18 ++++++++++-------- src/libslic3r/TriangleSelector.hpp | 7 ++++--- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 93cb8747a6..3b032cc57e 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -280,7 +280,7 @@ int TriangleSelector::select_unsplit_triangle(const Vec3f &hit, int facet_idx) c return this->select_unsplit_triangle(hit, facet_idx, neighbors); } -void TriangleSelector::select_patch(int facet_start, std::unique_ptr &&cursor, EnforcerBlockerType new_state, const Transform3d& trafo_no_translate, bool triangle_splitting, float highlight_by_angle_deg) +void TriangleSelector::select_patch(int facet_start, std::unique_ptr &&cursor, EnforcerBlockerType new_state, const Transform3d& trafo_no_translate, bool triangle_splitting, float highlight_by_angle_deg, const bool select_partially) { assert(facet_start < m_orig_size_indices); @@ -343,7 +343,7 @@ void TriangleSelector::select_patch(int facet_start, std::unique_ptr &&c Matrix3f normal_matrix = static_cast(trafo_no_translate.matrix().block(0, 0, 3, 3).inverse().transpose().cast()); float world_normal_z = (normal_matrix* facet_normal).normalized().z(); if (!visited[facet] && (highlight_by_angle_deg == 0.f || world_normal_z < highlight_angle_limit)) { - if (select_triangle(facet, new_state, triangle_splitting)) { + if (select_triangle(facet, new_state, triangle_splitting, select_partially)) { // add neighboring facets to list to be processed later for (int neighbor_idx : m_neighbors[facet]) if (neighbor_idx >= 0 && m_cursor->is_facet_visible(neighbor_idx, m_face_normals)) @@ -599,7 +599,7 @@ void TriangleSelector::bucket_fill_select_triangles(const Vec3f& hit, int facet_ // This is done by an actual recursive call. Returns false if the triangle is // outside the cursor. // Called by select_patch() and by itself. -bool TriangleSelector::select_triangle(int facet_idx, EnforcerBlockerType type, bool triangle_splitting) +bool TriangleSelector::select_triangle(int facet_idx, EnforcerBlockerType type, bool triangle_splitting, bool select_partially) { assert(facet_idx < int(m_triangles.size())); @@ -609,7 +609,7 @@ bool TriangleSelector::select_triangle(int facet_idx, EnforcerBlockerType type, Vec3i32 neighbors = m_neighbors[facet_idx]; assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors)); - if (! select_triangle_recursive(facet_idx, neighbors, type, triangle_splitting)) + if (! select_triangle_recursive(facet_idx, neighbors, type, triangle_splitting, select_partially)) return false; // In case that all children are leafs and have the same state now, @@ -949,7 +949,7 @@ Vec3i32 TriangleSelector::child_neighbors_propagated(const Triangle &tr, const V return out; } -bool TriangleSelector::select_triangle_recursive(int facet_idx, const Vec3i32 &neighbors, EnforcerBlockerType type, bool triangle_splitting) +bool TriangleSelector::select_triangle_recursive(int facet_idx, const Vec3i32 &neighbors, EnforcerBlockerType type, bool triangle_splitting, bool select_partially) { assert(facet_idx < int(m_triangles.size())); @@ -982,8 +982,10 @@ bool TriangleSelector::select_triangle_recursive(int facet_idx, const Vec3i32 &n if (triangle_splitting) split_triangle(facet_idx, neighbors); - if (!m_triangles[facet_idx].is_split()) + if ((!triangle_splitting || select_partially) && !m_triangles[facet_idx].is_split()) { m_triangles[facet_idx].set_state(type); + return true; + } tr = &m_triangles[facet_idx]; // might have been invalidated by split_triangle(). int num_of_children = tr->number_of_split_sides() + 1; @@ -993,7 +995,7 @@ bool TriangleSelector::select_triangle_recursive(int facet_idx, const Vec3i32 &n assert(tr->children[i] < int(m_triangles.size())); // Recursion, deep first search over the children of this triangle. // All children of this triangle were created by splitting a single source triangle of the original mesh. - select_triangle_recursive(tr->children[i], this->child_neighbors(*tr, neighbors, i), type, triangle_splitting); + select_triangle_recursive(tr->children[i], this->child_neighbors(*tr, neighbors, i), type, triangle_splitting, select_partially); tr = &m_triangles[facet_idx]; // might have been invalidated } } @@ -2513,7 +2515,7 @@ TriangleSelector::TriangleSplittingData TriangleSelector::remap_painting( if (TriangleCursor::check_normal(norm_b, -norm_a) && check_overlap(pv0, pv1, pv2, ta, tb, tc)) { // Paint this face target_selector.select_patch(face_idx, TriangleCursor::build_cursor(source_selector, tri), tri.get_state(), - Transform3d::Identity(), true); + Transform3d::Identity(), true, 0.f, true); } return true; // continue traversal }); diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index 09b606b5d2..11517f5c6c 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -308,7 +308,8 @@ public: EnforcerBlockerType new_state, // enforcer or blocker? const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation bool triangle_splitting, // If triangles will be split base on the cursor or not - float highlight_by_angle_deg = 0.f); // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees. + float highlight_by_angle_deg = 0.f, // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees. + bool select_partially = false); // Select a triangle if it's partially in the cursor but too small to be subdivided void seed_fill_select_triangles(const Vec3f &hit, // point where to start int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to @@ -496,8 +497,8 @@ protected: // Private functions: private: - bool select_triangle(int facet_idx, EnforcerBlockerType type, bool triangle_splitting); - bool select_triangle_recursive(int facet_idx, const Vec3i32 &neighbors, EnforcerBlockerType type, bool triangle_splitting); + bool select_triangle(int facet_idx, EnforcerBlockerType type, bool triangle_splitting, bool select_partially); + bool select_triangle_recursive(int facet_idx, const Vec3i32 &neighbors, EnforcerBlockerType type, bool triangle_splitting, bool select_partially); void undivide_triangle(int facet_idx); void split_triangle(int facet_idx, const Vec3i32 &neighbors); void remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant. From 51d2c91c9b10108f16d58bb97d3b8a4d2102c745 Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Sun, 10 May 2026 16:26:19 +0800 Subject: [PATCH 023/117] Remap paint after reload from disk --- src/slic3r/GUI/Plater.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index e95d428248..c876ca457e 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -8880,6 +8880,16 @@ void Plater::priv::reload_from_disk() new_volume->convert_from_imperial_units(); else if (old_volume->source.is_converted_from_meters) new_volume->convert_from_meters(); + + // Remap paint + if (wxGetApp().app_config->get_bool("keep_painting")) { + auto saved_painting = old_volume->save_painting(); + if (saved_painting) { + saved_painting->mesh.transform(Geometry::translation_transform(new_volume->mesh().get_init_shift())); + new_volume->restore_painting(saved_painting); + } + } + std::swap(old_model_object->volumes[vol_idx], old_model_object->volumes.back()); old_model_object->delete_volume(old_model_object->volumes.size() - 1); if (!sinking) old_model_object->ensure_on_bed(); From 5c754e360e72c04d1091fb4188367b70f3bad4c4 Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Sun, 10 May 2026 16:33:08 +0800 Subject: [PATCH 024/117] Spatial paint remapping after replace stl --- src/slic3r/GUI/Plater.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index c876ca457e..e184ad6272 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -8351,10 +8351,20 @@ bool Plater::priv::replace_volume_with_stl(int object_idx, int volume_idx, const new_volume->convert_from_imperial_units(); else if (old_volume->source.is_converted_from_meters) new_volume->convert_from_meters(); - new_volume->supported_facets.assign(old_volume->supported_facets); - new_volume->seam_facets.assign(old_volume->seam_facets); - new_volume->mmu_segmentation_facets.assign(old_volume->mmu_segmentation_facets); - new_volume->fuzzy_skin_facets.assign(old_volume->fuzzy_skin_facets); + if (wxGetApp().app_config->get_bool("keep_painting")) { + // Proper paint remapping + auto saved_painting = old_volume->save_painting(); + if (saved_painting) { + saved_painting->mesh.transform(Geometry::translation_transform(new_volume->mesh().get_init_shift())); + new_volume->restore_painting(saved_painting); + } + } else { + // Won't work well if mesh changed, but kept for old behavior + new_volume->supported_facets.assign(old_volume->supported_facets); + new_volume->seam_facets.assign(old_volume->seam_facets); + new_volume->mmu_segmentation_facets.assign(old_volume->mmu_segmentation_facets); + new_volume->fuzzy_skin_facets.assign(old_volume->fuzzy_skin_facets); + } std::swap(old_model_object->volumes[volume_idx], old_model_object->volumes.back()); old_model_object->delete_volume(old_model_object->volumes.size() - 1); if (!sinking) From 1299fcef4ae397a2f56e05ca81b76355cbec9628 Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Sun, 10 May 2026 18:27:02 +0800 Subject: [PATCH 025/117] Remap paint after repair model --- src/slic3r/GUI/GUI_ObjectList.cpp | 7 +++++-- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 7 ++++--- src/slic3r/Utils/FixModelByCgal.cpp | 18 +++++++++++++++--- src/slic3r/Utils/FixModelByCgal.hpp | 2 +- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 9da5edf6c3..93e5c94432 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -6086,10 +6086,13 @@ void ObjectList::fix_through_cgal() msg += "\n"; } - plater->clear_before_change_mesh(obj_idx); + const bool keep_painting = GUI::wxGetApp().app_config->get_bool("keep_painting"); + if (!keep_painting) { + plater->clear_before_change_mesh(obj_idx); + } const size_t volumes_before = object(obj_idx)->volumes.size(); std::string res; - if (!fix_model_with_cgal_gui(*(object(obj_idx)), vol_idx, progress_dlg, msg, res)) + if (!fix_model_with_cgal_gui(*(object(obj_idx)), vol_idx, progress_dlg, msg, res, keep_painting)) return false; //wxGetApp().plater()->changed_mesh(obj_idx); object(obj_idx)->ensure_on_bed(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index c2e4a19a73..da523788b8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -3321,6 +3321,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) wxBusyCursor wait; + const bool keep_painting = GUI::wxGetApp().app_config->get_bool("keep_painting"); ModelObjectCutAttributes attributes = only_if(has_connectors ? true : m_keep_upper, ModelObjectCutAttribute::KeepUpper) | only_if(has_connectors ? true : m_keep_lower, ModelObjectCutAttribute::KeepLower) | only_if(has_connectors ? false : m_keep_as_parts, ModelObjectCutAttribute::KeepAsParts) | @@ -3330,7 +3331,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower) | only_if(dowels_count > 0, ModelObjectCutAttribute::CreateDowels) | only_if(!has_connectors && !cut_with_groove && cut_mo->cut_id.id().invalid(), ModelObjectCutAttribute::InvalidateCutInfo) | - only_if(wxGetApp().app_config->get_bool("keep_painting"),ModelObjectCutAttribute::KeepPaint); + only_if(keep_painting, ModelObjectCutAttribute::KeepPaint); // update cut_id for the cut object in respect to the attributes update_object_cut_id(cut_mo->cut_id, attributes, dowels_count); @@ -3363,12 +3364,12 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) // model_name failing reason std::vector> failed_models; auto plater = wxGetApp().plater(); - auto fix_and_update_progress = [this, plater](ModelObject *model_object, const int vol_idx, const string &model_name, ProgressDialog &progress_dlg, + auto fix_and_update_progress = [this, plater, keep_painting](ModelObject *model_object, const int vol_idx, const string &model_name, ProgressDialog &progress_dlg, std::vector &succes_models, std::vector> &failed_models) { wxString msg = _L("Repairing model object"); msg += ": " + from_u8(model_name) + "\n"; std::string res; - if (!fix_model_with_cgal_gui(*model_object, vol_idx, progress_dlg, msg, res)) return false; + if (!fix_model_with_cgal_gui(*model_object, vol_idx, progress_dlg, msg, res, keep_painting)) return false; return true; }; ProgressDialog progress_dlg(_L("Repairing model object"), "", 100, find_toplevel_parent(plater), wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT, true); diff --git a/src/slic3r/Utils/FixModelByCgal.cpp b/src/slic3r/Utils/FixModelByCgal.cpp index e1f4320fec..9e2e014d93 100644 --- a/src/slic3r/Utils/FixModelByCgal.cpp +++ b/src/slic3r/Utils/FixModelByCgal.cpp @@ -13,6 +13,7 @@ #include "libslic3r/MeshBoolean.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/format.hpp" +#include "libslic3r/Thread.hpp" #include "../GUI/I18N.hpp" // Orca: This file provides utilities for repairing 3D model meshes using the CGAL library, handling mesh splitting, merging, and boolean operations. @@ -66,7 +67,7 @@ public: // Orca: Main function to repair model objects using CGAL, with progress dialog and cancellation support. // Returns false if fixing was canceled. fix_result contains error message if failed. -bool fix_model_with_cgal_gui(ModelObject &model_object, int volume_idx, GUI::ProgressDialog &progress_dialog, const wxString &msg_header, std::string &fix_result) +bool fix_model_with_cgal_gui(ModelObject &model_object, int volume_idx, GUI::ProgressDialog &progress_dialog, const wxString &msg_header, std::string &fix_result, bool keep_painting) { // Orca: Synchronization primitives for progress updates between worker thread and GUI. std::mutex mtx; @@ -94,8 +95,10 @@ bool fix_model_with_cgal_gui(ModelObject &model_object, int volume_idx, GUI::Pro }; // Orca: Worker thread that performs the actual model repair operations. - auto worker_thread = std::thread([&model_object, volume_idx, &ivolume, on_progress, &success, &canceled, &finished, &fix_result]() { + auto worker_thread = std::thread([&model_object, volume_idx, &ivolume, on_progress, &success, &canceled, &finished, &fix_result, keep_painting]() { try { + set_current_thread_name("cgal_fix_model"); + size_t start_volume = volume_idx == -1 ? 0 : size_t(volume_idx); size_t end_volume = volume_idx == -1 ? std::numeric_limits::max() : size_t(volume_idx); @@ -112,7 +115,7 @@ bool fix_model_with_cgal_gui(ModelObject &model_object, int volume_idx, GUI::Pro // Orca: Split splittable volumes into parts for individual processing. size_t parts_count = 1; if (volume->is_splittable()) { - parts_count = volume->split(1, false); // TODO: fix this + parts_count = volume->split(1, keep_painting); if (parts_count > 1) { const std::string msg = Slic3r::format(L("Split into %1% parts"), parts_count); on_progress(msg.c_str(), 10); @@ -150,6 +153,12 @@ bool fix_model_with_cgal_gui(ModelObject &model_object, int volume_idx, GUI::Pro ModelVolume *part_volume = model_object.volumes[part_idx]; TriangleMesh mesh = part_volume->mesh(); if (its_num_open_edges(mesh.its) != 0) { + + // Save painting for later remap + const std::optional saved_painting = keep_painting ? + part_volume->save_painting() : + std::optional{}; + std::string error; if (!MeshBoolean::cgal::repair(mesh, nullptr, &error)) throw Slic3r::RuntimeError(error.empty() ? L("Repair failed") : error.c_str()); @@ -158,6 +167,9 @@ bool fix_model_with_cgal_gui(ModelObject &model_object, int volume_idx, GUI::Pro part_volume->calculate_convex_hull(); part_volume->invalidate_convex_hull_2d(); part_volume->set_new_unique_id(); + + // Remap paint back + part_volume->restore_painting(saved_painting); } } diff --git a/src/slic3r/Utils/FixModelByCgal.hpp b/src/slic3r/Utils/FixModelByCgal.hpp index 3caa6d7fb9..61c8c91087 100644 --- a/src/slic3r/Utils/FixModelByCgal.hpp +++ b/src/slic3r/Utils/FixModelByCgal.hpp @@ -11,7 +11,7 @@ class ModelObject; class Print; // Return false if fixing was canceled. fix_result is empty on success. -extern bool fix_model_with_cgal_gui(ModelObject &model_object, int volume_idx, GUI::ProgressDialog &progress_dlg, const wxString &msg_header, std::string &fix_result); +extern bool fix_model_with_cgal_gui(ModelObject &model_object, int volume_idx, GUI::ProgressDialog &progress_dlg, const wxString &msg_header, std::string &fix_result, bool keep_painting); } // namespace Slic3r From a0d1ccaf28262b1e5e7e2f6e8235fa17482bde50 Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Sun, 10 May 2026 20:01:50 +0800 Subject: [PATCH 026/117] Refactor planar cut code so it can be reused by other cut type --- src/libslic3r/CutUtils.cpp | 64 +++++++++++++++++++++++++------------- src/libslic3r/Model.hpp | 1 + 2 files changed, 44 insertions(+), 21 deletions(-) diff --git a/src/libslic3r/CutUtils.cpp b/src/libslic3r/CutUtils.cpp index 7fc21bb3b4..9fc6f13c2a 100644 --- a/src/libslic3r/CutUtils.cpp +++ b/src/libslic3r/CutUtils.cpp @@ -8,7 +8,6 @@ #include "ObjectID.hpp" #include -#include namespace Slic3r { @@ -62,9 +61,10 @@ static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelV assert(vol->config.id() != src_volume->config.id()); vol->set_material(src_volume->material_id(), *src_volume->material()); vol->cut_info = src_volume->cut_info; + vol->cut_info.source_volume = src_volume->id(); } -static void process_volume_cut( ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, +static void process_volume_cut( const ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh) { const auto volume_matrix = volume->get_matrix(); @@ -179,9 +179,8 @@ static void process_modifier_cut(ModelVolume* volume, const Transform3d& instanc lower->add_volume(*volume); } -static void process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, - ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, - const std::optional& saved_painting) +static void process_solid_part_cut(const ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, + ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower) { // Perform cut TriangleMesh upper_mesh, lower_mesh; @@ -190,27 +189,19 @@ static void process_solid_part_cut(ModelVolume* volume, const Transform3d& insta // Add required cut parts to the objects if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) { - if (!upper_mesh.empty()) { - add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A"); - upper->volumes.back()->restore_painting(saved_painting); - } + add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A"); if (!lower_mesh.empty()) { add_cut_volume(lower_mesh, upper, volume, cut_matrix, "_B"); upper->volumes.back()->cut_info.is_from_upper = false; - upper->volumes.back()->restore_painting(saved_painting); } return; } - if (attributes.has(ModelObjectCutAttribute::KeepUpper) && !upper_mesh.empty()) { + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) add_cut_volume(upper_mesh, upper, volume, cut_matrix); - upper->volumes.back()->restore_painting(saved_painting); - } - if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty()) { + if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty()) add_cut_volume(lower_mesh, lower, volume, cut_matrix); - lower->volumes.back()->restore_painting(saved_painting); - } } static void reset_instance_transformation(ModelObject* object, size_t src_instance_idx, @@ -259,8 +250,12 @@ Cut::Cut(const ModelObject* object, int instance, const Transform3d& cut_matrix, : m_instance(instance), m_cut_matrix(cut_matrix), m_attributes(attributes) { m_model = Model(); - if (object) - m_model.add_object(*object); + if (object) { + const auto obj = m_model.add_object(*object); + for (int i = 0; i < obj->volumes.size(); i++) { + obj->volumes[i]->cut_info.source_volume = object->volumes[i]->id(); + } + } } void Cut::post_process(ModelObject* object, ModelObjectPtrs& cut_object_ptrs, bool keep, bool place_on_cut, bool flip) @@ -291,7 +286,20 @@ void Cut::post_process(ModelObject* upper, ModelObject* lower, ModelObjectPtrs& void Cut::finalize(const ModelObjectPtrs& objects) { - //clear model from temporarry objects + for (const auto obj : objects) { + for (const auto v : obj->volumes) { + if (v->cut_info.source_volume.valid()) { + for (const auto src : m_model.objects.front()->volumes) { + if (src->id() == v->cut_info.source_volume) { + v->cut_info.source_volume = src->cut_info.source_volume; + break; + } + } + } + } + } + + //clear model from temporary objects m_model.clear_objects(); // add to model result objects @@ -350,8 +358,22 @@ const ModelObjectPtrs& Cut::perform_with_plane() else process_connector_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower, dowels); } - else if (!volume->mesh().empty()) - process_solid_part_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower, saved_painting); + else if (!volume->mesh().empty()) { + process_solid_part_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower); + + // Apply paint to volumes cut from current volume + if (saved_painting) { + auto apply_paint = [&saved_painting, &volume](const ModelObject* obj) { + for (const auto v : obj->volumes) { + if (v->cut_info.source_volume == volume->id()) { + v->restore_painting(saved_painting); + } + } + }; + apply_paint(upper); + apply_paint(lower); + } + } } // Post-process cut parts diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index d8697adb41..eeec2783d3 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -825,6 +825,7 @@ public: CutConnectorType connector_type{ CutConnectorType::Plug }; float radius_tolerance{ 0.f };// [0.f : 1.f] float height_tolerance{ 0.f };// [0.f : 1.f] + ObjectID source_volume; // id of the volume from input model that this one is cut from CutInfo() = default; CutInfo(CutConnectorType type, float rad_tolerance, float h_tolerance, bool processed = false) : From f3fe37eab387533b5b1c3bf15cea5cd3df648799 Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Sun, 10 May 2026 23:08:40 +0800 Subject: [PATCH 027/117] Remap paint after dovetail cut --- src/libslic3r/CutUtils.cpp | 71 +++++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/CutUtils.cpp b/src/libslic3r/CutUtils.cpp index 9fc6f13c2a..fbc4d93c3e 100644 --- a/src/libslic3r/CutUtils.cpp +++ b/src/libslic3r/CutUtils.cpp @@ -451,15 +451,23 @@ static void merge_solid_parts_inside_object(ModelObjectPtrs& objects) for (ModelObject* mo : objects) { TriangleMesh mesh; // Merge all SolidPart but not Connectors + std::vector> saved_paintings; for (const ModelVolume* mv : mo->volumes) { if (mv->is_model_part() && !mv->is_cut_connector()) { TriangleMesh m = mv->mesh(); m.transform(mv->get_matrix()); mesh.merge(m); + saved_paintings.emplace_back(mv->save_painting()); + if (saved_paintings.back()) { + saved_paintings.back()->mesh = std::move(m); + } } } if (!mesh.empty()) { - ModelVolume* new_volume = mo->add_volume(mesh); + ModelVolume* new_volume = mo->add_volume(std::move(mesh)); + for (const auto& saved_painting : saved_paintings) { + new_volume->restore_painting(saved_painting, true); + } new_volume->name = mo->name; // Delete all merged SolidPart but not Connectors for (int i = int(mo->volumes.size()) - 2; i >= 0; --i) { @@ -583,6 +591,11 @@ const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Tran tmp_model.add_object(*cut_mo); ModelObject* tmp_object = tmp_model.objects.front(); + // Store source volume id so we could track them back after the cut + for (int i = 0; i < cut_mo->volumes.size(); i++) { + tmp_object->volumes[i]->cut_info.source_volume = cut_mo->volumes[i]->id(); + } + auto add_volumes_from_cut = [](ModelObject* object, const ModelObjectCutAttribute attribute, const Model& tmp_model_for_cut) { const auto& volumes = tmp_model_for_cut.objects.front()->volumes; for (const ModelVolume* volume : volumes) @@ -603,6 +616,20 @@ const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Tran tmp_model_for_cut.add_object(*cut.perform_with_plane().front()); assert(!tmp_model_for_cut.objects.empty()); + // Track back the volume id + for (const auto v : tmp_model_for_cut.objects.front()->volumes) { + auto& src_id = v->cut_info.source_volume; + if (src_id.valid()) { + // Find the src volume + for (const auto volume : object->volumes) { + if (src_id == volume->id()) { + src_id = volume->cut_info.source_volume; + break; + } + } + } + } + object->clear_volumes(); add_volumes_from_cut(object, add_volumes_attribute, tmp_model_for_cut); reset_instance_transformation(object, m_instance); @@ -663,6 +690,48 @@ const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Tran // this part can be added to the upper object now add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + // Paint volumes + if (m_attributes.has(ModelObjectCutAttribute::KeepPaint)) { + const auto instance_matrix = cut_mo->instances[m_instance]->get_transformation().get_matrix_no_offset(); + std::map> saved_paintings; + auto get_painting = [&saved_paintings, &cut_mo, &instance_matrix](const ObjectID& volume_id) -> std::optional& { + const auto it = saved_paintings.find(volume_id); + if (it != saved_paintings.end()) { + return it->second; + } + + // Export paintings from volume + const auto volume = std::find_if(cut_mo->volumes.begin(), cut_mo->volumes.end(), + [&volume_id](const ModelVolume* v) { return volume_id == v->id(); }); + + std::optional saved_painting = (*volume)->save_painting(); + if (saved_painting) { + // Transform mesh to cut space (same transform as process_volume_cut applies) + const auto volume_matrix = (*volume)->get_matrix(); + saved_painting->mesh.transform(instance_matrix * volume_matrix, true); + } + + saved_paintings.emplace(volume_id, std::move(saved_painting)); + + return saved_paintings[volume_id]; + }; + + auto paint_volumes = [&get_painting](const ModelObject* object) { + for (const auto volume : object->volumes) { + if (volume->cut_info.source_volume.valid()) { + volume->restore_painting(get_painting(volume->cut_info.source_volume)); + } + } + }; + + if (m_attributes.has(ModelObjectCutAttribute::KeepUpper)) { + paint_volumes(upper); + } + if (m_attributes.has(ModelObjectCutAttribute::KeepLower)) { + paint_volumes(lower); + } + } + ModelObjectPtrs cut_object_ptrs; if (keep_as_parts) { From 89ab4d27cb2175cdce3e2218c7c2057da6914532 Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Mon, 11 May 2026 14:19:03 +0800 Subject: [PATCH 028/117] Revert "Remap paint after dovetail cut" This reverts commit f3fe37eab387533b5b1c3bf15cea5cd3df648799. --- src/libslic3r/CutUtils.cpp | 71 +------------------------------------- 1 file changed, 1 insertion(+), 70 deletions(-) diff --git a/src/libslic3r/CutUtils.cpp b/src/libslic3r/CutUtils.cpp index fbc4d93c3e..9fc6f13c2a 100644 --- a/src/libslic3r/CutUtils.cpp +++ b/src/libslic3r/CutUtils.cpp @@ -451,23 +451,15 @@ static void merge_solid_parts_inside_object(ModelObjectPtrs& objects) for (ModelObject* mo : objects) { TriangleMesh mesh; // Merge all SolidPart but not Connectors - std::vector> saved_paintings; for (const ModelVolume* mv : mo->volumes) { if (mv->is_model_part() && !mv->is_cut_connector()) { TriangleMesh m = mv->mesh(); m.transform(mv->get_matrix()); mesh.merge(m); - saved_paintings.emplace_back(mv->save_painting()); - if (saved_paintings.back()) { - saved_paintings.back()->mesh = std::move(m); - } } } if (!mesh.empty()) { - ModelVolume* new_volume = mo->add_volume(std::move(mesh)); - for (const auto& saved_painting : saved_paintings) { - new_volume->restore_painting(saved_painting, true); - } + ModelVolume* new_volume = mo->add_volume(mesh); new_volume->name = mo->name; // Delete all merged SolidPart but not Connectors for (int i = int(mo->volumes.size()) - 2; i >= 0; --i) { @@ -591,11 +583,6 @@ const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Tran tmp_model.add_object(*cut_mo); ModelObject* tmp_object = tmp_model.objects.front(); - // Store source volume id so we could track them back after the cut - for (int i = 0; i < cut_mo->volumes.size(); i++) { - tmp_object->volumes[i]->cut_info.source_volume = cut_mo->volumes[i]->id(); - } - auto add_volumes_from_cut = [](ModelObject* object, const ModelObjectCutAttribute attribute, const Model& tmp_model_for_cut) { const auto& volumes = tmp_model_for_cut.objects.front()->volumes; for (const ModelVolume* volume : volumes) @@ -616,20 +603,6 @@ const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Tran tmp_model_for_cut.add_object(*cut.perform_with_plane().front()); assert(!tmp_model_for_cut.objects.empty()); - // Track back the volume id - for (const auto v : tmp_model_for_cut.objects.front()->volumes) { - auto& src_id = v->cut_info.source_volume; - if (src_id.valid()) { - // Find the src volume - for (const auto volume : object->volumes) { - if (src_id == volume->id()) { - src_id = volume->cut_info.source_volume; - break; - } - } - } - } - object->clear_volumes(); add_volumes_from_cut(object, add_volumes_attribute, tmp_model_for_cut); reset_instance_transformation(object, m_instance); @@ -690,48 +663,6 @@ const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Tran // this part can be added to the upper object now add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); - // Paint volumes - if (m_attributes.has(ModelObjectCutAttribute::KeepPaint)) { - const auto instance_matrix = cut_mo->instances[m_instance]->get_transformation().get_matrix_no_offset(); - std::map> saved_paintings; - auto get_painting = [&saved_paintings, &cut_mo, &instance_matrix](const ObjectID& volume_id) -> std::optional& { - const auto it = saved_paintings.find(volume_id); - if (it != saved_paintings.end()) { - return it->second; - } - - // Export paintings from volume - const auto volume = std::find_if(cut_mo->volumes.begin(), cut_mo->volumes.end(), - [&volume_id](const ModelVolume* v) { return volume_id == v->id(); }); - - std::optional saved_painting = (*volume)->save_painting(); - if (saved_painting) { - // Transform mesh to cut space (same transform as process_volume_cut applies) - const auto volume_matrix = (*volume)->get_matrix(); - saved_painting->mesh.transform(instance_matrix * volume_matrix, true); - } - - saved_paintings.emplace(volume_id, std::move(saved_painting)); - - return saved_paintings[volume_id]; - }; - - auto paint_volumes = [&get_painting](const ModelObject* object) { - for (const auto volume : object->volumes) { - if (volume->cut_info.source_volume.valid()) { - volume->restore_painting(get_painting(volume->cut_info.source_volume)); - } - } - }; - - if (m_attributes.has(ModelObjectCutAttribute::KeepUpper)) { - paint_volumes(upper); - } - if (m_attributes.has(ModelObjectCutAttribute::KeepLower)) { - paint_volumes(lower); - } - } - ModelObjectPtrs cut_object_ptrs; if (keep_as_parts) { From 4aeb6a31db561e13ea6cdb2d8eae19b07132e900 Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Mon, 11 May 2026 14:19:08 +0800 Subject: [PATCH 029/117] Revert "Refactor planar cut code so it can be reused by other cut type" This reverts commit a0d1ccaf28262b1e5e7e2f6e8235fa17482bde50. --- src/libslic3r/CutUtils.cpp | 64 +++++++++++++------------------------- src/libslic3r/Model.hpp | 1 - 2 files changed, 21 insertions(+), 44 deletions(-) diff --git a/src/libslic3r/CutUtils.cpp b/src/libslic3r/CutUtils.cpp index 9fc6f13c2a..7fc21bb3b4 100644 --- a/src/libslic3r/CutUtils.cpp +++ b/src/libslic3r/CutUtils.cpp @@ -8,6 +8,7 @@ #include "ObjectID.hpp" #include +#include namespace Slic3r { @@ -61,10 +62,9 @@ static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelV assert(vol->config.id() != src_volume->config.id()); vol->set_material(src_volume->material_id(), *src_volume->material()); vol->cut_info = src_volume->cut_info; - vol->cut_info.source_volume = src_volume->id(); } -static void process_volume_cut( const ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, +static void process_volume_cut( ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh) { const auto volume_matrix = volume->get_matrix(); @@ -179,8 +179,9 @@ static void process_modifier_cut(ModelVolume* volume, const Transform3d& instanc lower->add_volume(*volume); } -static void process_solid_part_cut(const ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, - ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower) +static void process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, + ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, + const std::optional& saved_painting) { // Perform cut TriangleMesh upper_mesh, lower_mesh; @@ -189,19 +190,27 @@ static void process_solid_part_cut(const ModelVolume* volume, const Transform3d& // Add required cut parts to the objects if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) { - add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A"); + if (!upper_mesh.empty()) { + add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A"); + upper->volumes.back()->restore_painting(saved_painting); + } if (!lower_mesh.empty()) { add_cut_volume(lower_mesh, upper, volume, cut_matrix, "_B"); upper->volumes.back()->cut_info.is_from_upper = false; + upper->volumes.back()->restore_painting(saved_painting); } return; } - if (attributes.has(ModelObjectCutAttribute::KeepUpper)) + if (attributes.has(ModelObjectCutAttribute::KeepUpper) && !upper_mesh.empty()) { add_cut_volume(upper_mesh, upper, volume, cut_matrix); + upper->volumes.back()->restore_painting(saved_painting); + } - if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty()) + if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty()) { add_cut_volume(lower_mesh, lower, volume, cut_matrix); + lower->volumes.back()->restore_painting(saved_painting); + } } static void reset_instance_transformation(ModelObject* object, size_t src_instance_idx, @@ -250,12 +259,8 @@ Cut::Cut(const ModelObject* object, int instance, const Transform3d& cut_matrix, : m_instance(instance), m_cut_matrix(cut_matrix), m_attributes(attributes) { m_model = Model(); - if (object) { - const auto obj = m_model.add_object(*object); - for (int i = 0; i < obj->volumes.size(); i++) { - obj->volumes[i]->cut_info.source_volume = object->volumes[i]->id(); - } - } + if (object) + m_model.add_object(*object); } void Cut::post_process(ModelObject* object, ModelObjectPtrs& cut_object_ptrs, bool keep, bool place_on_cut, bool flip) @@ -286,20 +291,7 @@ void Cut::post_process(ModelObject* upper, ModelObject* lower, ModelObjectPtrs& void Cut::finalize(const ModelObjectPtrs& objects) { - for (const auto obj : objects) { - for (const auto v : obj->volumes) { - if (v->cut_info.source_volume.valid()) { - for (const auto src : m_model.objects.front()->volumes) { - if (src->id() == v->cut_info.source_volume) { - v->cut_info.source_volume = src->cut_info.source_volume; - break; - } - } - } - } - } - - //clear model from temporary objects + //clear model from temporarry objects m_model.clear_objects(); // add to model result objects @@ -358,22 +350,8 @@ const ModelObjectPtrs& Cut::perform_with_plane() else process_connector_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower, dowels); } - else if (!volume->mesh().empty()) { - process_solid_part_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower); - - // Apply paint to volumes cut from current volume - if (saved_painting) { - auto apply_paint = [&saved_painting, &volume](const ModelObject* obj) { - for (const auto v : obj->volumes) { - if (v->cut_info.source_volume == volume->id()) { - v->restore_painting(saved_painting); - } - } - }; - apply_paint(upper); - apply_paint(lower); - } - } + else if (!volume->mesh().empty()) + process_solid_part_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower, saved_painting); } // Post-process cut parts diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index eeec2783d3..d8697adb41 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -825,7 +825,6 @@ public: CutConnectorType connector_type{ CutConnectorType::Plug }; float radius_tolerance{ 0.f };// [0.f : 1.f] float height_tolerance{ 0.f };// [0.f : 1.f] - ObjectID source_volume; // id of the volume from input model that this one is cut from CutInfo() = default; CutInfo(CutConnectorType type, float rad_tolerance, float h_tolerance, bool processed = false) : From 66f8d954af8babd5de33e3ddd448e78dd1c8f7b3 Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Mon, 11 May 2026 14:29:17 +0800 Subject: [PATCH 030/117] Simplify cut repaint code to make it more generic --- src/libslic3r/CutUtils.cpp | 60 ++++++++++++++++++++------------------ src/libslic3r/CutUtils.hpp | 2 +- 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/src/libslic3r/CutUtils.cpp b/src/libslic3r/CutUtils.cpp index 7fc21bb3b4..35be5246de 100644 --- a/src/libslic3r/CutUtils.cpp +++ b/src/libslic3r/CutUtils.cpp @@ -8,7 +8,6 @@ #include "ObjectID.hpp" #include -#include namespace Slic3r { @@ -64,7 +63,7 @@ static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelV vol->cut_info = src_volume->cut_info; } -static void process_volume_cut( ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, +static void process_volume_cut( const ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh) { const auto volume_matrix = volume->get_matrix(); @@ -179,9 +178,8 @@ static void process_modifier_cut(ModelVolume* volume, const Transform3d& instanc lower->add_volume(*volume); } -static void process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, - ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, - const std::optional& saved_painting) +static void process_solid_part_cut(const ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, + ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower) { // Perform cut TriangleMesh upper_mesh, lower_mesh; @@ -190,27 +188,19 @@ static void process_solid_part_cut(ModelVolume* volume, const Transform3d& insta // Add required cut parts to the objects if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) { - if (!upper_mesh.empty()) { - add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A"); - upper->volumes.back()->restore_painting(saved_painting); - } + add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A"); if (!lower_mesh.empty()) { add_cut_volume(lower_mesh, upper, volume, cut_matrix, "_B"); upper->volumes.back()->cut_info.is_from_upper = false; - upper->volumes.back()->restore_painting(saved_painting); } return; } - if (attributes.has(ModelObjectCutAttribute::KeepUpper) && !upper_mesh.empty()) { + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) add_cut_volume(upper_mesh, upper, volume, cut_matrix); - upper->volumes.back()->restore_painting(saved_painting); - } - if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty()) { + if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty()) add_cut_volume(lower_mesh, lower, volume, cut_matrix); - lower->volumes.back()->restore_painting(saved_painting); - } } static void reset_instance_transformation(ModelObject* object, size_t src_instance_idx, @@ -289,9 +279,22 @@ void Cut::post_process(ModelObject* upper, ModelObject* lower, ModelObjectPtrs& } -void Cut::finalize(const ModelObjectPtrs& objects) +void Cut::finalize(const ModelObjectPtrs& objects, const std::vector>& saved_paintings) { - //clear model from temporarry objects + // Paint volumes + for (const auto& saved_painting : saved_paintings) { + if (saved_painting) { + for (const auto object : objects) { + for (const auto volume : object->volumes) { + if (volume->is_model_part() && !volume->is_cut_connector()) { + volume->restore_painting(saved_painting, true); + } + } + } + } + } + + //clear model from temporary objects m_model.clear_objects(); // add to model result objects @@ -330,15 +333,14 @@ const ModelObjectPtrs& Cut::perform_with_plane() const Transformation cut_transformation = Transformation(m_cut_matrix); const Transform3d inverse_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1. * cut_transformation.get_offset()); + std::vector> saved_paintings; for (ModelVolume* volume : mo->volumes) { // Save painting data before reset_extra_facets() discards it. - std::optional saved_painting; if (m_attributes.has(ModelObjectCutAttribute::KeepPaint)) { - saved_painting = volume->save_painting(); - if (saved_painting) { + saved_paintings.emplace_back(volume->save_painting()); + if (saved_paintings.back()) { // Transform mesh to cut space (same transform as process_volume_cut applies) - const auto volume_matrix = volume->get_matrix(); - saved_painting->mesh.transform(instance_matrix * volume_matrix, true); + saved_paintings.back()->mesh.transform(instance_matrix * volume->get_matrix(), true); } } @@ -351,7 +353,7 @@ const ModelObjectPtrs& Cut::perform_with_plane() process_connector_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower, dowels); } else if (!volume->mesh().empty()) - process_solid_part_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower, saved_painting); + process_solid_part_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower); } // Post-process cut parts @@ -397,9 +399,9 @@ const ModelObjectPtrs& Cut::perform_with_plane() } } - BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end"; + finalize(cut_object_ptrs, saved_paintings); - finalize(cut_object_ptrs); + BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end"; return m_model.objects; } @@ -496,7 +498,7 @@ const ModelObjectPtrs& Cut::perform_by_contour(std::vector parts, int dowe merge_solid_parts_inside_object(cut_object_ptrs); // replace initial objects in model with cut object - finalize(cut_object_ptrs); + finalize(cut_object_ptrs, {}); } else if (volumes.size() > cut_parts_cnt) { // Means that object is cut with connectors @@ -527,7 +529,7 @@ const ModelObjectPtrs& Cut::perform_by_contour(std::vector parts, int dowe merge_solid_parts_inside_object(cut_object_ptrs); // replace initial objects in model with cut object - finalize(cut_object_ptrs); + finalize(cut_object_ptrs, {}); // Add Dowel-connectors as separate objects to model if (cut_connectors_obj.size() >= 3) @@ -679,7 +681,7 @@ const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Tran merge_solid_parts_inside_object(cut_object_ptrs); } - finalize(cut_object_ptrs); + finalize(cut_object_ptrs, {}); return m_model.objects; } diff --git a/src/libslic3r/CutUtils.hpp b/src/libslic3r/CutUtils.hpp index f39007c8a9..d7e486beaf 100644 --- a/src/libslic3r/CutUtils.hpp +++ b/src/libslic3r/CutUtils.hpp @@ -25,7 +25,7 @@ class Cut { void post_process(ModelObject* object, ModelObjectPtrs& objects, bool keep, bool place_on_cut, bool flip); void post_process(ModelObject* upper_object, ModelObject* lower_object, ModelObjectPtrs& objects); - void finalize(const ModelObjectPtrs& objects); + void finalize(const ModelObjectPtrs& objects, const std::vector>& saved_paintings); public: From b9c9f42f013dc680e68d80f4bc3a7f78fc408a1e Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Mon, 11 May 2026 14:53:53 +0800 Subject: [PATCH 031/117] Remap paint after dovetail cut --- src/libslic3r/CutUtils.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/CutUtils.cpp b/src/libslic3r/CutUtils.cpp index 35be5246de..d192a854bb 100644 --- a/src/libslic3r/CutUtils.cpp +++ b/src/libslic3r/CutUtils.cpp @@ -555,6 +555,20 @@ const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Tran upper->name = upper->name + "_A"; lower->name = lower->name + "_B"; } + + // Save painting data so we later can remap it. + std::vector> saved_paintings; + if (m_attributes.has(ModelObjectCutAttribute::KeepPaint)) { + const auto instance_matrix = cut_mo->instances[m_instance]->get_transformation().get_matrix_no_offset(); + for (const auto volume : cut_mo->volumes) { + saved_paintings.emplace_back(volume->save_painting()); + if (saved_paintings.back()) { + // Transform mesh to cut space (same transform as process_volume_cut applies) + saved_paintings.back()->mesh.transform(instance_matrix * volume->get_matrix(), true); + } + } + } + const double groove_half_depth = 0.5 * double(groove.depth); Model tmp_model_for_cut = Model(); @@ -681,7 +695,7 @@ const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Tran merge_solid_parts_inside_object(cut_object_ptrs); } - finalize(cut_object_ptrs, {}); + finalize(cut_object_ptrs, saved_paintings); return m_model.objects; } From ea5119ec3f89af6334d8ec3723fb87456d5a277d Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Mon, 11 May 2026 15:33:09 +0800 Subject: [PATCH 032/117] Remap paint after contour cut (cut with part assigned to other side using right mouse click) --- src/libslic3r/CutUtils.cpp | 19 ++++++++++++++++--- src/libslic3r/CutUtils.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 4 ++-- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/CutUtils.cpp b/src/libslic3r/CutUtils.cpp index d192a854bb..5899313035 100644 --- a/src/libslic3r/CutUtils.cpp +++ b/src/libslic3r/CutUtils.cpp @@ -454,7 +454,7 @@ static void merge_solid_parts_inside_object(ModelObjectPtrs& objects) } -const ModelObjectPtrs& Cut::perform_by_contour(std::vector parts, int dowels_count) +const ModelObjectPtrs& Cut::perform_by_contour(const ModelObject* src_object, std::vector parts, int dowels_count) { ModelObject* cut_mo = m_model.objects.front(); @@ -469,6 +469,19 @@ const ModelObjectPtrs& Cut::perform_by_contour(std::vector parts, int dowe lower->name = lower->name + "_B"; } + // Save painting data so we later can remap it. + std::vector> saved_paintings; + if (m_attributes.has(ModelObjectCutAttribute::KeepPaint)) { + const auto instance_matrix = src_object->instances[m_instance]->get_transformation().get_matrix_no_offset(); + for (const auto volume : src_object->volumes) { + saved_paintings.emplace_back(volume->save_painting()); + if (saved_paintings.back()) { + // Transform mesh to cut space (same transform as process_volume_cut applies) + saved_paintings.back()->mesh.transform(instance_matrix * volume->get_matrix(), true); + } + } + } + const size_t cut_parts_cnt = parts.size(); bool has_modifiers = false; @@ -498,7 +511,7 @@ const ModelObjectPtrs& Cut::perform_by_contour(std::vector parts, int dowe merge_solid_parts_inside_object(cut_object_ptrs); // replace initial objects in model with cut object - finalize(cut_object_ptrs, {}); + finalize(cut_object_ptrs, saved_paintings); } else if (volumes.size() > cut_parts_cnt) { // Means that object is cut with connectors @@ -529,7 +542,7 @@ const ModelObjectPtrs& Cut::perform_by_contour(std::vector parts, int dowe merge_solid_parts_inside_object(cut_object_ptrs); // replace initial objects in model with cut object - finalize(cut_object_ptrs, {}); + finalize(cut_object_ptrs, saved_paintings); // Add Dowel-connectors as separate objects to model if (cut_connectors_obj.size() >= 3) diff --git a/src/libslic3r/CutUtils.hpp b/src/libslic3r/CutUtils.hpp index d7e486beaf..99cdb9cd65 100644 --- a/src/libslic3r/CutUtils.hpp +++ b/src/libslic3r/CutUtils.hpp @@ -56,7 +56,7 @@ public: }; const ModelObjectPtrs& perform_with_plane(); - const ModelObjectPtrs& perform_by_contour(std::vector parts, int dowels_count); + const ModelObjectPtrs& perform_by_contour(const ModelObject* src_object, std::vector parts, int dowels_count); const ModelObjectPtrs& perform_with_groove(const Groove& groove, const Transform3d& rotation_m, bool keep_as_parts = false); }; // namespace Cut diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index da523788b8..f042656ab7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -1908,7 +1908,7 @@ GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* mo, const Transfor // split to parts for (int id = int(volumes.size())-1; id >= 0; id--) if (volumes[id]->is_splittable()) - volumes[id]->split(1, false); // TODO: fix this + volumes[id]->split(1, false); // No need to remap paint here, we do it later in perform_by_contour m_parts.clear(); for (const ModelVolume* volume : volumes) { @@ -3337,7 +3337,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) update_object_cut_id(cut_mo->cut_id, attributes, dowels_count); Cut cut(cut_mo, instance_idx, get_cut_matrix(selection), attributes); - const ModelObjectPtrs& new_objects = cut_by_contour ? cut.perform_by_contour(m_part_selection.get_cut_parts(), dowels_count): + const ModelObjectPtrs& new_objects = cut_by_contour ? cut.perform_by_contour(mo, m_part_selection.get_cut_parts(), dowels_count): cut_with_groove ? cut.perform_with_groove(m_groove, m_rotation_m) : cut.perform_with_plane(); From 3cbdb092d6e2de0d9f02c44261ddbcff0ddfa757 Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Mon, 11 May 2026 17:00:31 +0800 Subject: [PATCH 033/117] Always clear existing facets first if not kept --- src/libslic3r/Model.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 000ab997a9..0067684f15 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1997,6 +1997,10 @@ std::optional ModelVolume::save_painting() cons void ModelVolume::restore_painting(const std::optional& saved, const bool keep_existing_paint) { + if (!keep_existing_paint) { + reset_extra_facets(); + } + if (!saved) { return; } From b4b29821069393d537a5c374f272ed714afda36a Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Mon, 11 May 2026 17:00:54 +0800 Subject: [PATCH 034/117] Remap paint after simplify --- src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index 1f8af3bf09..95936556d4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -535,12 +535,20 @@ void GLGizmoSimplify::apply_simplify() { auto plater = wxGetApp().plater(); plater->take_snapshot(GUI::format("Simplify %1%", m_volume->name)); - plater->clear_before_change_mesh(object_idx); + const bool keep_painting = GUI::wxGetApp().app_config->get_bool("keep_painting"); + if (!keep_painting) { + plater->clear_before_change_mesh(object_idx); + } ModelVolume* mv = get_model_volume(selection, wxGetApp().model()); assert(mv == m_volume); + // Save paint + std::optional saved_painting = keep_painting ? mv->save_painting() : + std::optional{}; mv->set_mesh(std::move(*m_state.result)); + // Remap paint + mv->restore_painting(saved_painting); m_state.result.reset(); mv->calculate_convex_hull(); mv->invalidate_convex_hull_2d(); From 5b1766b1d59e93e532c70dad901239191c8a664b Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Mon, 11 May 2026 17:16:36 +0800 Subject: [PATCH 035/117] Remap paint after smooth --- src/slic3r/GUI/GUI_ObjectList.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 93e5c94432..3cee3d2058 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -6210,6 +6210,7 @@ void GUI::ObjectList::smooth_mesh() WarningDialog dlg(static_cast(wxGetApp().mainframe), content, wxEmptyString, wxOK); dlg.ShowModal(); }; + const bool keep_painting = GUI::wxGetApp().app_config->get_bool("keep_painting"); bool has_show_smooth_mesh_error_dlg = false; if (vol_idxs.empty()) { obj = object(object_idx); @@ -6221,8 +6222,11 @@ void GUI::ObjectList::smooth_mesh() bool ok; auto result_mesh = TriangleMeshDeal::smooth_triangle_mesh(mv->mesh(), ok); if (ok) { + const std::optional saved_painting = keep_painting ? + mv->save_painting() : + std::optional{}; mv->set_mesh(result_mesh); - mv->reset_extra_facets(); // reset paint color + mv->restore_painting(saved_painting); mv->calculate_convex_hull(); mv->invalidate_convex_hull_2d(); mv->set_new_unique_id(); @@ -6247,8 +6251,11 @@ void GUI::ObjectList::smooth_mesh() bool ok; auto result_mesh = TriangleMeshDeal::smooth_triangle_mesh(mv->mesh(),ok); if (ok) { + const std::optional saved_painting = keep_painting ? + mv->save_painting() : + std::optional{}; mv->set_mesh(result_mesh); - mv->reset_extra_facets(); // reset paint color + mv->restore_painting(saved_painting); mv->calculate_convex_hull(); mv->invalidate_convex_hull_2d(); mv->set_new_unique_id(); From 873d0ed26ec587707d71e2803a3bce7aa5d7bedd Mon Sep 17 00:00:00 2001 From: Ian Bassi Date: Sun, 17 May 2026 02:31:51 -0300 Subject: [PATCH 036/117] Graphics Preferences: Anti-aliasing + FPS (#13538) * Expose Antialiasing velues Expose Antialiasing multipliers. Default to 4 as current implementation but enables the user to disable it to improve performante or increase sampling to improve quality. * FXAA * Improve descriptions * Require restart when MSAA setting changes Detect changes to the OpenGL MSAA (multisample anti-aliasing) preference when opening Preferences and prompt the user to restart the application to apply the change. Adds a constant key for the MSAA setting, stores the previous value, checks for changes (similar to the existing FXAA handling), and shows a warning dialog explaining the restart will close the current project without saving. If the user accepts, recreate_GUI is invoked to apply the MSAA change immediately. * Revert "Require restart when MSAA setting changes" This reverts commit dde134d346c3849598c91d025d2faed1b51c8a22. * Menu and FPS options * Fix FPS limiter and remove VSYNC * Grouped FPS settings and mode up rigth fps counter --------- Co-authored-by: Noisyfox --- resources/shaders/110/fxaa.fs | 50 +++++++++++ resources/shaders/110/fxaa.vs | 15 ++++ resources/shaders/140/fxaa.fs | 52 +++++++++++ resources/shaders/140/fxaa.vs | 15 ++++ src/libslic3r/AppConfig.cpp | 23 +++++ src/libslic3r/AppConfig.hpp | 4 + src/slic3r/GUI/GLCanvas3D.cpp | 130 +++++++++++++++++++++++++++- src/slic3r/GUI/GLCanvas3D.hpp | 8 ++ src/slic3r/GUI/GLShadersManager.cpp | 2 + src/slic3r/GUI/GUI_App.cpp | 15 ++++ src/slic3r/GUI/OpenGLManager.cpp | 25 ++++-- src/slic3r/GUI/Preferences.cpp | 61 ++++++++++++- 12 files changed, 393 insertions(+), 7 deletions(-) create mode 100644 resources/shaders/110/fxaa.fs create mode 100644 resources/shaders/110/fxaa.vs create mode 100644 resources/shaders/140/fxaa.fs create mode 100644 resources/shaders/140/fxaa.vs diff --git a/resources/shaders/110/fxaa.fs b/resources/shaders/110/fxaa.fs new file mode 100644 index 0000000000..c4db1a36ad --- /dev/null +++ b/resources/shaders/110/fxaa.fs @@ -0,0 +1,50 @@ +#version 110 + +uniform sampler2D uniform_texture; +uniform vec2 inv_tex_size; + +varying vec2 tex_coord; + +void main() +{ + vec3 rgbNW = texture2D(uniform_texture, tex_coord + vec2(-1.0, -1.0) * inv_tex_size).rgb; + vec3 rgbNE = texture2D(uniform_texture, tex_coord + vec2(1.0, -1.0) * inv_tex_size).rgb; + vec3 rgbSW = texture2D(uniform_texture, tex_coord + vec2(-1.0, 1.0) * inv_tex_size).rgb; + vec3 rgbSE = texture2D(uniform_texture, tex_coord + vec2(1.0, 1.0) * inv_tex_size).rgb; + vec3 rgbM = texture2D(uniform_texture, tex_coord).rgb; + + vec3 luma_coeff = vec3(0.299, 0.587, 0.114); + float lumaNW = dot(rgbNW, luma_coeff); + float lumaNE = dot(rgbNE, luma_coeff); + float lumaSW = dot(rgbSW, luma_coeff); + float lumaSE = dot(rgbSE, luma_coeff); + float lumaM = dot(rgbM, luma_coeff); + + float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE))); + float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE))); + + vec2 dir; + dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE)); + dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE)); + + const float FXAA_REDUCE_MIN = 1.0 / 128.0; + const float FXAA_REDUCE_MUL = 1.0 / 8.0; + const float FXAA_SPAN_MAX = 8.0; + + float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN); + float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce); + dir = min(vec2(FXAA_SPAN_MAX), max(vec2(-FXAA_SPAN_MAX), dir * rcpDirMin)) * inv_tex_size; + + vec3 rgbA = 0.5 * ( + texture2D(uniform_texture, tex_coord + dir * (1.0 / 3.0 - 0.5)).rgb + + texture2D(uniform_texture, tex_coord + dir * (2.0 / 3.0 - 0.5)).rgb + ); + + vec3 rgbB = rgbA * 0.5 + 0.25 * ( + texture2D(uniform_texture, tex_coord + dir * -0.5).rgb + + texture2D(uniform_texture, tex_coord + dir * 0.5).rgb + ); + + float lumaB = dot(rgbB, luma_coeff); + gl_FragColor = (lumaB < lumaMin || lumaB > lumaMax) ? vec4(rgbA, 1.0) : vec4(rgbB, 1.0); +} diff --git a/resources/shaders/110/fxaa.vs b/resources/shaders/110/fxaa.vs new file mode 100644 index 0000000000..f066b5dc5f --- /dev/null +++ b/resources/shaders/110/fxaa.vs @@ -0,0 +1,15 @@ +#version 110 + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; + +attribute vec3 v_position; +attribute vec2 v_tex_coord; + +varying vec2 tex_coord; + +void main() +{ + tex_coord = v_tex_coord; + gl_Position = projection_matrix * view_model_matrix * vec4(v_position, 1.0); +} diff --git a/resources/shaders/140/fxaa.fs b/resources/shaders/140/fxaa.fs new file mode 100644 index 0000000000..5b2c034ea7 --- /dev/null +++ b/resources/shaders/140/fxaa.fs @@ -0,0 +1,52 @@ +#version 140 + +uniform sampler2D uniform_texture; +uniform vec2 inv_tex_size; + +in vec2 tex_coord; + +out vec4 out_color; + +void main() +{ + vec3 rgbNW = texture(uniform_texture, tex_coord + vec2(-1.0, -1.0) * inv_tex_size).rgb; + vec3 rgbNE = texture(uniform_texture, tex_coord + vec2(1.0, -1.0) * inv_tex_size).rgb; + vec3 rgbSW = texture(uniform_texture, tex_coord + vec2(-1.0, 1.0) * inv_tex_size).rgb; + vec3 rgbSE = texture(uniform_texture, tex_coord + vec2(1.0, 1.0) * inv_tex_size).rgb; + vec3 rgbM = texture(uniform_texture, tex_coord).rgb; + + vec3 luma_coeff = vec3(0.299, 0.587, 0.114); + float lumaNW = dot(rgbNW, luma_coeff); + float lumaNE = dot(rgbNE, luma_coeff); + float lumaSW = dot(rgbSW, luma_coeff); + float lumaSE = dot(rgbSE, luma_coeff); + float lumaM = dot(rgbM, luma_coeff); + + float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE))); + float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE))); + + vec2 dir; + dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE)); + dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE)); + + const float FXAA_REDUCE_MIN = 1.0 / 128.0; + const float FXAA_REDUCE_MUL = 1.0 / 8.0; + const float FXAA_SPAN_MAX = 8.0; + + float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN); + float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce); + dir = min(vec2(FXAA_SPAN_MAX), max(vec2(-FXAA_SPAN_MAX), dir * rcpDirMin)) * inv_tex_size; + + vec3 rgbA = 0.5 * ( + texture(uniform_texture, tex_coord + dir * (1.0 / 3.0 - 0.5)).rgb + + texture(uniform_texture, tex_coord + dir * (2.0 / 3.0 - 0.5)).rgb + ); + + vec3 rgbB = rgbA * 0.5 + 0.25 * ( + texture(uniform_texture, tex_coord + dir * -0.5).rgb + + texture(uniform_texture, tex_coord + dir * 0.5).rgb + ); + + float lumaB = dot(rgbB, luma_coeff); + out_color = (lumaB < lumaMin || lumaB > lumaMax) ? vec4(rgbA, 1.0) : vec4(rgbB, 1.0); +} diff --git a/resources/shaders/140/fxaa.vs b/resources/shaders/140/fxaa.vs new file mode 100644 index 0000000000..5ca1517db0 --- /dev/null +++ b/resources/shaders/140/fxaa.vs @@ -0,0 +1,15 @@ +#version 140 + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; + +in vec3 v_position; +in vec2 v_tex_coord; + +out vec2 tex_coord; + +void main() +{ + tex_coord = v_tex_coord; + gl_Position = projection_matrix * view_model_matrix * vec4(v_position, 1.0); +} diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 55918e86c2..33e25f5d9c 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -230,6 +230,29 @@ void AppConfig::set_defaults() if (get("camera_orbit_mult").empty()) set("camera_orbit_mult", "1.0"); + if (get(SETTING_OPENGL_AA_SAMPLES).empty()) + set(SETTING_OPENGL_AA_SAMPLES, "4"); + + if (get(SETTING_OPENGL_FXAA_ENABLED).empty()) + set_bool(SETTING_OPENGL_FXAA_ENABLED, false); + + if (get(SETTING_OPENGL_FPS_CAP).empty()) + set(SETTING_OPENGL_FPS_CAP, "0"); + else { + int fps_cap = 0; + try { + fps_cap = std::stoi(get(SETTING_OPENGL_FPS_CAP)); + } + catch (...) { + fps_cap = 0; + } + fps_cap = std::max(0, std::min(fps_cap, 240)); + set(SETTING_OPENGL_FPS_CAP, std::to_string(fps_cap)); + } + + if (get(SETTING_OPENGL_SHOW_FPS_OVERLAY).empty()) + set_bool(SETTING_OPENGL_SHOW_FPS_OVERLAY, false); + if (get("export_sources_full_pathnames").empty()) set_bool("export_sources_full_pathnames", false); diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp index 06b919484f..33f243905a 100644 --- a/src/libslic3r/AppConfig.hpp +++ b/src/libslic3r/AppConfig.hpp @@ -30,6 +30,10 @@ using namespace nlohmann; #define SETTING_NETWORK_PLUGIN_REMIND_LATER "network_plugin_remind_later" #define SETTING_USE_ENCRYPTED_TOKEN_FILE "use_encrypted_token_file" #define SETTING_CLOUD_PROVIDERS "cloud_providers" +#define SETTING_OPENGL_AA_SAMPLES "opengl_antialiasing_samples" +#define SETTING_OPENGL_FXAA_ENABLED "opengl_fxaa_enabled" +#define SETTING_OPENGL_FPS_CAP "opengl_fps_cap" +#define SETTING_OPENGL_SHOW_FPS_OVERLAY "opengl_show_fps_overlay" #if defined(_WIN32) || defined(_WIN64) #define BAMBU_NETWORK_AGENT_VERSION_LEGACY "01.10.01.09" diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index d50fa8ed97..b630f46794 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -14,6 +14,7 @@ #include "libslic3r/Technologies.hpp" #include "libslic3r/Tesselate.hpp" #include "libslic3r/PresetBundle.hpp" +#include "libslic3r/AppConfig.hpp" #include "3DScene.hpp" #include "BackgroundSlicingProcess.hpp" #include "GLShader.hpp" @@ -1202,6 +1203,11 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed) GLCanvas3D::~GLCanvas3D() { + if (m_fxaa_texture_id != 0 && _set_current()) { + glsafe(::glDeleteTextures(1, &m_fxaa_texture_id)); + m_fxaa_texture_id = 0; + } + reset_volumes(); m_sel_plate_toolbar.del_all_item(); @@ -2082,9 +2088,16 @@ void GLCanvas3D::render(bool only_init) if (m_picking_enabled && m_rectangle_selection.is_dragging()) m_rectangle_selection.render(*this); + if (_is_fxaa_enabled()) + _render_fxaa_pass(static_cast(cnv_size.get_width()), static_cast(cnv_size.get_height())); + // draw overlays _render_overlays(); + const int current_fps = m_render_stats.get_fps_and_reset_if_needed(); + if (_is_fps_overlay_enabled()) + _render_fps_overlay(current_fps); + if (wxGetApp().plater()->is_render_statistic_dialog_visible()) { ImGui::ShowMetricsWindow(); @@ -2092,7 +2105,7 @@ void GLCanvas3D::render(bool only_init) imgui.begin(std::string("Render statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); imgui.text("FPS (SwapBuffers() calls per second):"); ImGui::SameLine(); - imgui.text(std::to_string(m_render_stats.get_fps_and_reset_if_needed())); + imgui.text(std::to_string(current_fps)); ImGui::Separator(); imgui.text("Compressed textures:"); ImGui::SameLine(); @@ -3194,6 +3207,22 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt) wxGetApp().imgui()->reset_requires_extra_frame(); #endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT + const int fps_cap = _get_effective_fps_cap(); + if (fps_cap > 0) { + const auto now = std::chrono::steady_clock::now(); + const auto min_frame_time = std::chrono::duration(1.0 / static_cast(fps_cap)); + const auto elapsed = now - m_last_frame_start_time; + if (elapsed < min_frame_time) { + const int wait_ms = std::max(1, static_cast(std::ceil(std::chrono::duration(min_frame_time - elapsed).count()))); + schedule_extra_frame(wait_ms); + evt.RequestMore(); + return; + } + + // Pace by frame-start interval so rendering time is part of the target budget. + m_last_frame_start_time = now; + } + _refresh_if_shown_on_screen(); #if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT @@ -7422,6 +7451,105 @@ void GLCanvas3D::_rectangular_selection_picking_pass() _update_volumes_hover_state(); } +bool GLCanvas3D::_is_fxaa_enabled() const +{ + return wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_FXAA_ENABLED); +} + +int GLCanvas3D::_get_effective_fps_cap() const +{ + if (wxGetApp().app_config == nullptr) + return 0; + + int fps_cap = 0; + try { + fps_cap = std::stoi(wxGetApp().app_config->get(SETTING_OPENGL_FPS_CAP)); + } + catch (...) { + fps_cap = 0; + } + + fps_cap = std::max(0, std::min(fps_cap, 240)); + + return fps_cap; +} + +bool GLCanvas3D::_is_fps_overlay_enabled() const +{ + return wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_SHOW_FPS_OVERLAY); +} + +void GLCanvas3D::_render_fps_overlay(int fps) const +{ + if (fps < 0) + return; + + ImGuiWrapper& imgui = *wxGetApp().imgui(); + const float margin = 10.0f * get_scale(); + const ImVec2 display_size = ImGui::GetIO().DisplaySize; + ImGui::SetNextWindowPos(ImVec2(display_size.x - margin, margin), ImGuiCond_Always, ImVec2(1.0f, 0.0f)); + ImGui::SetNextWindowBgAlpha(0.35f); + imgui.begin( + std::string("###fps_overlay"), + ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoCollapse | + ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoSavedSettings | + ImGuiWindowFlags_NoInputs); + imgui.text(std::string("FPS: ") + std::to_string(fps)); + imgui.end(); +} + +void GLCanvas3D::_render_fxaa_pass(unsigned int width, unsigned int height) +{ + if (width == 0 || height == 0) + return; + + GLShaderProgram* shader = wxGetApp().get_shader("fxaa"); + if (shader == nullptr) + return; + + if (m_fxaa_texture_id == 0) { + glsafe(::glGenTextures(1, &m_fxaa_texture_id)); + glsafe(::glBindTexture(GL_TEXTURE_2D, m_fxaa_texture_id)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); + glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); + } + + glsafe(::glBindTexture(GL_TEXTURE_2D, m_fxaa_texture_id)); + if (m_fxaa_texture_size[0] != width || m_fxaa_texture_size[1] != height) { + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); + m_fxaa_texture_size = { width, height }; + } + + glsafe(::glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, width, height)); + + glsafe(::glDisable(GL_DEPTH_TEST)); + glsafe(::glDisable(GL_BLEND)); + glsafe(::glClear(GL_COLOR_BUFFER_BIT)); + + shader->start_using(); + shader->set_uniform("view_model_matrix", Transform3d::Identity()); + shader->set_uniform("projection_matrix", Transform3d::Identity()); + shader->set_uniform("uniform_texture", 0); + shader->set_uniform("inv_tex_size", Vec2f(1.0f / static_cast(width), 1.0f / static_cast(height))); + + glsafe(::glActiveTexture(GL_TEXTURE0)); + glsafe(::glBindTexture(GL_TEXTURE_2D, m_fxaa_texture_id)); + m_background.render(); + glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); + shader->stop_using(); + + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); +} + void GLCanvas3D::_render_background() { bool use_error_color = false; diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index db0bb5181a..f6c42bdaa9 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -607,6 +607,7 @@ private: bool m_reload_delayed; RenderStats m_render_stats; + std::chrono::time_point m_last_frame_start_time{ std::chrono::steady_clock::now() }; int m_imgui_undo_redo_hovered_pos{ -1 }; int m_mouse_wheel{ 0 }; @@ -724,6 +725,8 @@ public: CameraTarget m_camera_target; #endif // ENABLE_SHOW_CAMERA_TARGET GLModel m_background; + unsigned int m_fxaa_texture_id{ 0 }; + std::array m_fxaa_texture_size{ 0, 0 }; public: explicit GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed); ~GLCanvas3D(); @@ -1231,6 +1234,11 @@ private: void _picking_pass(); void _rectangular_selection_picking_pass(); + bool _is_fxaa_enabled() const; + int _get_effective_fps_cap() const; + bool _is_fps_overlay_enabled() const; + void _render_fps_overlay(int fps) const; + void _render_fxaa_pass(unsigned int width, unsigned int height); void _render_background(); void _render_bed(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_axes); //BBS: add part plate related logic diff --git a/src/slic3r/GUI/GLShadersManager.cpp b/src/slic3r/GUI/GLShadersManager.cpp index 7f28d8f777..620fc2e773 100644 --- a/src/slic3r/GUI/GLShadersManager.cpp +++ b/src/slic3r/GUI/GLShadersManager.cpp @@ -48,6 +48,8 @@ std::pair GLShadersManager::init() valid &= append_shader("flat_clip", { prefix + "flat_clip.vs", prefix + "flat_clip.fs" }); // basic shader for textures, used to render textures valid &= append_shader("flat_texture", { prefix + "flat_texture.vs", prefix + "flat_texture.fs" }); + // used to apply post-processing antialiasing in screen space + valid &= append_shader("fxaa", { prefix + "fxaa.vs", prefix + "fxaa.fs" }); // used to render 3D scene background valid &= append_shader("background", { prefix + "background.vs", prefix + "background.fs" }); #if SLIC3R_OPENGL_ES diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index c12fa964f8..3169e53eee 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -7553,6 +7553,13 @@ void GUI_App::open_exportpresetbundledialog(size_t open_on_tab, const std::strin void GUI_App::open_preferences(size_t open_on_tab, const std::string& highlight_option) { + static constexpr const char* opengl_fxaa_setting_key = "opengl_fxaa_enabled"; + static constexpr const char* opengl_fps_cap_setting_key = "opengl_fps_cap"; + static constexpr const char* opengl_show_fps_overlay_setting_key = "opengl_show_fps_overlay"; + const std::string previous_opengl_fxaa = app_config->get(opengl_fxaa_setting_key); + const std::string previous_opengl_fps_cap = app_config->get(opengl_fps_cap_setting_key); + const std::string previous_opengl_show_fps_overlay = app_config->get(opengl_show_fps_overlay_setting_key); + bool need_recreate_gui = false; std::string pending_language; { @@ -7591,6 +7598,14 @@ void GUI_App::open_preferences(size_t open_on_tab, const std::string& highlight_ } } + const bool opengl_fxaa_changed = app_config->get(opengl_fxaa_setting_key) != previous_opengl_fxaa; + const bool opengl_fps_cap_changed = app_config->get(opengl_fps_cap_setting_key) != previous_opengl_fps_cap; + const bool opengl_show_fps_overlay_changed = app_config->get(opengl_show_fps_overlay_setting_key) != previous_opengl_show_fps_overlay; + if ((opengl_fxaa_changed || opengl_fps_cap_changed || opengl_show_fps_overlay_changed) && !need_recreate_gui && this->plater_ != nullptr) { + this->plater_->set_current_canvas_as_dirty(); + this->plater_->get_current_canvas3D()->force_set_focus(); + } + if (!pending_language.empty()) { const std::string previous_language = app_config->get("language"); app_config->set("language", pending_language); diff --git a/src/slic3r/GUI/OpenGLManager.cpp b/src/slic3r/GUI/OpenGLManager.cpp index 638b8b01f5..2b5ebcd086 100644 --- a/src/slic3r/GUI/OpenGLManager.cpp +++ b/src/slic3r/GUI/OpenGLManager.cpp @@ -5,6 +5,7 @@ #include "I18N.hpp" #include "3DScene.hpp" +#include "libslic3r/AppConfig.hpp" #include "libslic3r/Platform.hpp" #include @@ -408,6 +409,13 @@ wxGLContext* OpenGLManager::init_glcontext(wxGLCanvas& canvas, const std::pairget(SETTING_OPENGL_AA_SAMPLES); + if (value == "0" || value == "2" || value == "4" || value == "8" || value == "16") + antialiasing_samples = ::atoi(value.c_str()); + } + int attribList[] = { WX_GL_RGBA, WX_GL_DOUBLEBUFFER, @@ -421,19 +429,26 @@ wxGLCanvas* OpenGLManager::create_wxglcanvas(wxWindow& parent) WX_GL_DEPTH_SIZE, 24, //BBS: turn on stencil buffer for outline WX_GL_STENCIL_SIZE, 8, - WX_GL_SAMPLE_BUFFERS, GL_TRUE, - WX_GL_SAMPLES, 4, + WX_GL_SAMPLE_BUFFERS, antialiasing_samples > 0 ? GL_TRUE : GL_FALSE, + WX_GL_SAMPLES, antialiasing_samples, 0 }; - if (s_multisample == EMultisampleState::Unknown) { + constexpr int sample_buffers_value_idx = 15; + constexpr int samples_value_idx = 17; + + if (antialiasing_samples <= 0) + s_multisample = EMultisampleState::Disabled; + else if (s_multisample == EMultisampleState::Unknown) { detect_multisample(attribList); // // debug output // std::cout << "Multisample " << (can_multisample() ? "enabled" : "disabled") << std::endl; } - if (! can_multisample()) - attribList[12] = 0; + if (! can_multisample()) { + attribList[sample_buffers_value_idx] = GL_FALSE; + attribList[samples_value_idx] = 0; + } wxGLCanvas* canvas = new wxGLCanvas(&parent, wxID_ANY, attribList, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS); // The GL canvas paints its entire surface, so background erasing is unnecessary. diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index bf1c69a7c0..f061de7bb3 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -114,7 +114,8 @@ wxBoxSizer *PreferencesDialog::create_item_combobox(wxString title, wxString too if (!current_setting.empty()) { auto compare = [current_setting](string possible_setting) { return current_setting == possible_setting; }; auto iterator = find_if(config_name_index.begin(), config_name_index.end(), compare); - current_index = iterator - config_name_index.begin(); + if (iterator != config_name_index.end()) + current_index = static_cast(iterator - config_name_index.begin()); } auto [sizer, combobox] = create_item_combobox_base(title, tooltip, param, vlist, current_index); @@ -1526,6 +1527,64 @@ void PreferencesDialog::create_items() g_sizer->AddSpacer(FromDIP(10)); sizer_page->Add(g_sizer, 0, wxEXPAND); + ////////////////////////// + //// GRAPHICS TAB + ///////////////////////////////////// + m_pref_tabs->AppendItem(_L("Graphics")); + f_sizers.push_back(new wxFlexGridSizer(1, 1, v_gap, 0)); + g_sizer = f_sizers.back(); + g_sizer->AddGrowableCol(0, 1); + + //// GRAPHICS > Anti-aliasing + g_sizer->Add(create_item_title(_L("Anti-aliasing")), 1, wxEXPAND); + + auto item_antialiasing = create_item_combobox( + _L("MSAA Multiplier"), + _L("Set the Multi-Sample Anti-Aliasing level.\n" + "Higher values result in smoother edges, but the impact on performance is exponential.\n" + "Lower values improve performance, at the cost of jagged edges.\n" + "If disabled, its recommended to enable FXAA to reduce jagged edges with minimal performance impact.\n\n" + "Requires application restart."), + SETTING_OPENGL_AA_SAMPLES, + {_L("Disabled"), "2x", "4x", "8x", "16x"}, + {"0", "2", "4", "8", "16"} + ); + g_sizer->Add(item_antialiasing); + + auto item_fxaa = create_item_checkbox( + _L("FXAA post-processing"), + _L("Applies Fast Approximate Anti-Aliasing as a screen-space pass.\n" + "Useful for disabling or reducing the MSAA setting to improve performance.\n\n" + "Takes effect immediately."), + SETTING_OPENGL_FXAA_ENABLED + ); + g_sizer->Add(item_fxaa); + + //// GRAPHICS > FPS + g_sizer->Add(create_item_title(_L("FPS")), 1, wxEXPAND); + + auto item_fps_cap = create_item_spinctrl( + _L("FPS cap"), + _L("(0 = unlimited)"), + _L("FPS"), + _L("Limits viewport frame rate to reduce GPU load and power usage.\n" + "Set to 0 for unlimited frame rate."), + SETTING_OPENGL_FPS_CAP, + 0, + 240 + ); + g_sizer->Add(item_fps_cap); + + auto item_fps_overlay = create_item_checkbox( + _L("Show FPS overlay"), + _L("Displays current viewport FPS in the top-right corner."), + SETTING_OPENGL_SHOW_FPS_OVERLAY + ); + g_sizer->Add(item_fps_overlay); + + g_sizer->AddSpacer(FromDIP(10)); + sizer_page->Add(g_sizer, 0, wxEXPAND); + ////////////////////////// //// ONLINE TAB ///////////////////////////////////// From da5a13a03262e42c56456d262703dc1fd99f0563 Mon Sep 17 00:00:00 2001 From: TheLegendTubaGuy Date: Sat, 16 May 2026 00:12:06 -0500 Subject: [PATCH 037/117] Fix DataViewBitmapText assignment --- src/slic3r/GUI/ExtraRenderers.cpp | 5 +- src/slic3r/GUI/ExtraRenderers.hpp | 9 ++ src/slic3r/GUI/GUI_ObjectList.cpp | 109 ++++++++++++++++++++++--- src/slic3r/GUI/GUI_ObjectList.hpp | 7 ++ src/slic3r/GUI/ObjectDataViewModel.cpp | 4 + 5 files changed, 121 insertions(+), 13 deletions(-) diff --git a/src/slic3r/GUI/ExtraRenderers.cpp b/src/slic3r/GUI/ExtraRenderers.cpp index 4c16ce8deb..28837dbdad 100644 --- a/src/slic3r/GUI/ExtraRenderers.cpp +++ b/src/slic3r/GUI/ExtraRenderers.cpp @@ -343,7 +343,10 @@ wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelR bool BitmapChoiceRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) { - ::ComboBox*c = static_cast<::ComboBox *>(ctrl); + auto* c = dynamic_cast<::ComboBox*>(ctrl); + if (!c) + return false; + int selection = c->GetSelection(); if (selection < 0) return false; diff --git a/src/slic3r/GUI/ExtraRenderers.hpp b/src/slic3r/GUI/ExtraRenderers.hpp index 18f1f49182..59a1f040ee 100644 --- a/src/slic3r/GUI/ExtraRenderers.hpp +++ b/src/slic3r/GUI/ExtraRenderers.hpp @@ -28,6 +28,15 @@ public: m_bmp(other.m_bmp) { } + DataViewBitmapText& operator=(const DataViewBitmapText &other) + { + if (this != &other) { + m_text = other.m_text; + m_bmp = other.m_bmp; + } + return *this; + } + void SetText(const wxString &text) { m_text = text; } wxString GetText() const { return m_text; } void SetBitmap(const wxBitmap &bmp) { m_bmp = bmp; } diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 3cee3d2058..d0afd8e295 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -435,15 +435,25 @@ void ObjectList::create_objects_ctrl() // column Extruder of the view control: BitmapChoiceRenderer* bmp_choice_renderer = new BitmapChoiceRenderer(); - bmp_choice_renderer->set_can_create_editor_ctrl_function([this]() { - return m_objects_model->GetItemType(GetSelection()) & (itVolume | itLayer | itObject); + const auto get_filament_context_item = [this]() { +#ifdef __WXOSX__ + return m_filament_editor_item.IsOk() ? m_filament_editor_item : GetSelection(); +#else + return GetSelection(); +#endif + }; + bmp_choice_renderer->set_can_create_editor_ctrl_function([this, get_filament_context_item]() { + const wxDataViewItem item = get_filament_context_item(); + return m_objects_model->GetItemType(item) & (itVolume | itLayer | itObject); }); - bmp_choice_renderer->set_default_extruder_idx([this]() { - return m_objects_model->GetDefaultExtruderIdx(GetSelection()); + bmp_choice_renderer->set_default_extruder_idx([this, get_filament_context_item]() { + const wxDataViewItem item = get_filament_context_item(); + return m_objects_model->GetDefaultExtruderIdx(item); }); - bmp_choice_renderer->set_has_default_extruder([this]() { - return m_objects_model->GetVolumeType(GetSelection()) == ModelVolumeType::PARAMETER_MODIFIER || - m_objects_model->GetItemType(GetSelection()) == itLayer; + bmp_choice_renderer->set_has_default_extruder([this, get_filament_context_item]() { + const wxDataViewItem item = get_filament_context_item(); + return m_objects_model->GetVolumeType(item) == ModelVolumeType::PARAMETER_MODIFIER || + m_objects_model->GetItemType(item) == itLayer; }); AppendColumn(new wxDataViewColumn(_L("Fila."), bmp_choice_renderer, colFilament, m_columns_width[colFilament] * em, wxALIGN_CENTER_HORIZONTAL, 0)); @@ -461,11 +471,16 @@ void ObjectList::create_objects_ctrl() wxALIGN_CENTER_HORIZONTAL, 0); - // Open filament editor faster + // On macOS, bypass wxDataViewCtrl::EditItem() for this custom renderer to avoid + // native text editing of wxCustomRendererObject. this->Bind(wxEVT_DATAVIEW_ITEM_ACTIVATED, [this](wxDataViewEvent& event) { if (event.GetColumn() == colFilament) { +#ifdef __WXOSX__ + start_filament_editor(event.GetItem()); +#else // Trigger the editor opening manually this->EditItem(event.GetItem(), GetColumn(colFilament)); +#endif } }); @@ -1082,10 +1097,11 @@ void ObjectList::update_filament_in_config(const wxDataViewItem& item) if (m_prevent_update_filament_in_config) return; + ModelConfig* config = nullptr; const ItemType item_type = m_objects_model->GetItemType(item); if (item_type & itObject) { const int obj_idx = m_objects_model->GetIdByItem(item); - m_config = &(*m_objects)[obj_idx]->config; + config = &(*m_objects)[obj_idx]->config; } else { const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetObject(item)); @@ -1094,15 +1110,17 @@ void ObjectList::update_filament_in_config(const wxDataViewItem& item) if (obj_idx < 0 || ui_volume_idx < 0) return; int volume_in3d_idx = m_objects_model->get_real_volume_index_in_3d(obj_idx,ui_volume_idx); - m_config = &(*m_objects)[obj_idx]->volumes[volume_in3d_idx]->config; + config = &(*m_objects)[obj_idx]->volumes[volume_in3d_idx]->config; } else if (item_type & itLayer) - m_config = &get_item_config(item); + config = &get_item_config(item); } - if (!m_config) + if (!config) return; + m_config = config; + take_snapshot("Change Filament"); const int extruder = m_objects_model->GetExtruderNumber(item); @@ -6327,10 +6345,71 @@ void ObjectList::ItemValueChanged(wxDataViewEvent &event) } } +#ifdef __WXOSX__ +bool ObjectList::is_live_model_item(wxDataViewItem item) const +{ + if (!item.IsOk() || !m_objects_model) + return false; + + wxDataViewItemArray all_items; + m_objects_model->GetAllChildren(wxDataViewItem(nullptr), all_items); + for (const wxDataViewItem& live_item : all_items) + if (live_item.GetID() == item.GetID()) + return true; + + return false; +} + +void ObjectList::start_filament_editor(wxDataViewItem item) +{ + if (m_starting_filament_editor || !is_live_model_item(item)) + return; + + const ItemType type = m_objects_model->GetItemType(item); + if (!(type & (itVolume | itLayer | itObject))) + return; + + auto* column = GetColumn(colFilament); + if (!column) + return; + + auto* custom_renderer = dynamic_cast(column->GetRenderer()); + if (!custom_renderer || custom_renderer->GetEditorCtrl()) + return; + + m_starting_filament_editor = true; + m_filament_editor_item = item; + const bool started = custom_renderer->StartEditing(item, GetItemRect(item, column)); + m_filament_editor_item = wxDataViewItem(nullptr); + m_starting_filament_editor = false; + + if (!started) + return; + + SetCustomRendererPtr(custom_renderer); + SetCustomRendererItem(item); +} +#endif // __WXOSX__ + void GUI::ObjectList::OnStartEditing(wxDataViewEvent &event) { auto col = event.GetColumn(); auto item = event.GetItem(); +#ifdef __WXOSX__ + if (col == colFilament) { + if (m_starting_filament_editor) + return; + + event.Veto(); + CallAfter([this, item] { + if (!is_live_model_item(item)) + return; + start_filament_editor(item); + }); + return; + } +#endif // __WXOSX__ + if (col == colName) { ObjectDataViewModelNode* node = (ObjectDataViewModelNode*)item.GetID(); if (node->GetType() & itPlate) { @@ -6398,6 +6477,12 @@ void ObjectList::OnEditingStarted(wxDataViewEvent &event) dynamic_cast(wxGetApp().get_model_tab(vol_idx >= 0))->reset_model_config(); return; } +#ifdef __WXOSX__ + if (col == colFilament) { + start_filament_editor(item); + return; + } +#endif // __WXOSX__ if (col != colFilament && col != colName) return; auto column = GetColumn(col); diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index b97eff05ae..cab78b6adc 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -192,6 +192,13 @@ private: int m_last_selected_column = -1; #endif /* __MSW__ */ +#ifdef __WXOSX__ + bool m_starting_filament_editor = false; + wxDataViewItem m_filament_editor_item { nullptr }; + bool is_live_model_item(wxDataViewItem item) const; + void start_filament_editor(wxDataViewItem item); +#endif // __WXOSX__ + #if 0 SettingsFactory::Bundle m_freq_settings_fff; SettingsFactory::Bundle m_freq_settings_sla; diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index ed4ad60f55..7944718e59 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -332,12 +332,16 @@ bool ObjectDataViewModelNode::SetValue(const wxVariant& variant, unsigned col) m_variable_height_icon << variant; return true; case colName: { + if (variant.GetType() != wxT("DataViewBitmapText")) + return false; DataViewBitmapText data; data << variant; m_bmp = data.GetBitmap(); m_name = data.GetText(); return true; } case colFilament: { + if (variant.GetType() != wxT("DataViewBitmapText")) + return false; DataViewBitmapText data; data << variant; m_extruder_bmp = data.GetBitmap(); From bcbc581417843b543c3bcac99522d09a0ce23dc7 Mon Sep 17 00:00:00 2001 From: Matthew Nickerson <82531659+mwnickerson@users.noreply.github.com> Date: Sun, 17 May 2026 08:10:39 -0400 Subject: [PATCH 038/117] fix: harden CLI export without OpenGL thumbnails (#13532) ## Summary - skip CLI thumbnail generation when an OpenGL/GLFW context cannot be created, allowing export workflows to continue in headless/automation environments - guard filament variant remapping against missing config options and out-of-range variant indexes - log warnings instead of dereferencing invalid config state during CLI/profile-driven export ## Context These guards came out of automating OrcaSlicer CLI exports for printer workflows. In that flow, slicing/export can still be valid even when thumbnail rendering is unavailable, but the current path can proceed into OpenGL thumbnail setup after context creation fails. Separately, malformed or mismatched filament/profile state can index past option vectors during multi-filament value remapping. ## Test plan - `git diff --check` - `cmake --build build/arm64 --config RelWithDebInfo --target OrcaSlicer -- -j4` --- src/OrcaSlicer.cpp | 14 +++++++-- src/libslic3r/PrintConfig.cpp | 56 +++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/src/OrcaSlicer.cpp b/src/OrcaSlicer.cpp index 08335b9196..880eaffa4b 100644 --- a/src/OrcaSlicer.cpp +++ b/src/OrcaSlicer.cpp @@ -6435,6 +6435,7 @@ int CLI::run(int argc, char **argv) glfwGetVersion(&gl_major, &gl_minor, &gl_verbos); BOOST_LOG_TRIVIAL(info) << boost::format("opengl version %1%.%2%.%3%")%gl_major %gl_minor %gl_verbos; + bool thumbnail_opengl_ready = false; glfwSetErrorCallback(glfw_callback); int ret = glfwInit(); if (ret == GLFW_FALSE) { @@ -6463,13 +6464,22 @@ int CLI::run(int argc, char **argv) GLFWwindow* window = glfwCreateWindow(640, 480, "base_window", NULL, NULL); if (window == NULL) { - BOOST_LOG_TRIVIAL(error) << "Failed to create GLFW window" << std::endl; + BOOST_LOG_TRIVIAL(error) << "Failed to create GLFW window; skipping thumbnail rendering for CLI export" << std::endl; } - else + else { glfwMakeContextCurrent(window); + thumbnail_opengl_ready = true; + } } //opengl manager related logic + if (!thumbnail_opengl_ready) { + BOOST_LOG_TRIVIAL(error) << "OpenGL context unavailable; skip thumbnail generating" << std::endl; + need_create_thumbnail_group = false; + need_create_no_light_group = false; + need_create_top_group = false; + } + else { GUI::OpenGLManager opengl_mgr; bool opengl_valid = opengl_mgr.init_gl(false); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 21f2a22c79..ac712818ca 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -9589,11 +9589,19 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen case coStrings: { ConfigOptionStrings * opt = this->option(key); + if (!opt) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key; + break; + } std::vector new_values; new_values.resize(filament_count); for (int f_index = 0; f_index < filament_count; f_index++) { + if (variant_index[f_index] < 0 || static_cast(variant_index[f_index]) >= opt->size()) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index]; + continue; + } new_values[f_index] = opt->get_at(variant_index[f_index]); } opt->values = new_values; @@ -9602,11 +9610,19 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen case coInts: { ConfigOptionInts * opt = this->option(key); + if (!opt) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key; + break; + } std::vector new_values; new_values.resize(filament_count); for (int f_index = 0; f_index < filament_count; f_index++) { + if (variant_index[f_index] < 0 || static_cast(variant_index[f_index]) >= opt->size()) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index]; + continue; + } new_values[f_index] = opt->get_at(variant_index[f_index]); } opt->values = new_values; @@ -9615,11 +9631,19 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen case coFloats: { ConfigOptionFloats * opt = this->option(key); + if (!opt) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key; + break; + } std::vector new_values; new_values.resize(filament_count); for (int f_index = 0; f_index < filament_count; f_index++) { + if (variant_index[f_index] < 0 || static_cast(variant_index[f_index]) >= opt->size()) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index]; + continue; + } new_values[f_index] = opt->get_at(variant_index[f_index]); } opt->values = new_values; @@ -9628,11 +9652,19 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen case coPercents: { ConfigOptionPercents * opt = this->option(key); + if (!opt) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key; + break; + } std::vector new_values; new_values.resize(filament_count); for (int f_index = 0; f_index < filament_count; f_index++) { + if (variant_index[f_index] < 0 || static_cast(variant_index[f_index]) >= opt->size()) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index]; + continue; + } new_values[f_index] = opt->get_at(variant_index[f_index]); } opt->values = new_values; @@ -9641,11 +9673,19 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen case coFloatsOrPercents: { ConfigOptionFloatsOrPercents * opt = this->option(key); + if (!opt) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key; + break; + } std::vector new_values; new_values.resize(filament_count); for (int f_index = 0; f_index < filament_count; f_index++) { + if (variant_index[f_index] < 0 || static_cast(variant_index[f_index]) >= opt->size()) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index]; + continue; + } new_values[f_index] = opt->get_at(variant_index[f_index]); } opt->values = new_values; @@ -9654,11 +9694,19 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen case coBools: { ConfigOptionBools * opt = this->option(key); + if (!opt) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key; + break; + } std::vector new_values; new_values.resize(filament_count); for (int f_index = 0; f_index < filament_count; f_index++) { + if (variant_index[f_index] < 0 || static_cast(variant_index[f_index]) >= opt->size()) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index]; + continue; + } new_values[f_index] = opt->get_at(variant_index[f_index]); } opt->values = new_values; @@ -9667,11 +9715,19 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen case coEnums: { ConfigOptionEnumsGeneric * opt = this->option(key); + if (!opt) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key; + break; + } std::vector new_values; new_values.resize(filament_count); for (int f_index = 0; f_index < filament_count; f_index++) { + if (variant_index[f_index] < 0 || static_cast(variant_index[f_index]) >= opt->size()) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index]; + continue; + } new_values[f_index] = opt->get_at(variant_index[f_index]); } opt->values = new_values; From 484ce81e38c8176a6abe6a9744598697a6958635 Mon Sep 17 00:00:00 2001 From: SoftFever Date: Mon, 18 May 2026 01:18:09 +0800 Subject: [PATCH 039/117] Fix sidebar filament count for multi-extruder printers (#13706) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit load_selections() and update_selections() size the parallel project_config arrays (filament_colour, filament_colour_type, filament_map) off filament_presets.size(). When the saved list is shorter than the printer's nozzle count — never-used printer, hand-trimmed conf, or fewer filament_NN entries than nozzles — the loaded colors get truncated and the sidebar starts up one combo short. --- src/libslic3r/PresetBundle.cpp | 17 +++++++++++++++++ src/libslic3r/PresetBundle.hpp | 6 ++++++ 2 files changed, 23 insertions(+) diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 7d56bbe5e1..f14f1eb0a1 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -2614,6 +2614,9 @@ void PresetBundle::update_selections(AppConfig &config) break; this->filament_presets.emplace_back(remove_ini_suffix(f_name)); } + + update_filament_count(); + std::vector filament_colors; auto f_colors = config.get_printer_setting(initial_printer_profile_name, "filament_colors"); if (!f_colors.empty()) { @@ -2755,6 +2758,8 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p this->filament_presets.emplace_back(remove_ini_suffix(f_name)); } + update_filament_count(); + // Load data from AppConfig to ProjectConfig when Studio is initialized. std::vector filament_colors; auto f_colors = config.get_printer_setting(initial_printer_profile_name, "filament_colors"); @@ -3779,6 +3784,18 @@ int PresetBundle::get_printer_extruder_count() const return count; } +void PresetBundle::update_filament_count() +{ + if (printers.get_edited_preset().printer_technology() != ptFFF) + return; + const size_t num_extruders = static_cast(get_printer_extruder_count()); + if (filament_presets.size() >= num_extruders) + return; + filament_presets.resize(num_extruders, filament_presets.empty() + ? filaments.first_visible().name + : filament_presets.back()); +} + bool PresetBundle::support_different_extruders() { Preset& printer_preset = this->printers.get_edited_preset(); diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index 2351084ade..220de95b78 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -372,6 +372,12 @@ public: int get_printer_extruder_count() const; bool support_different_extruders(); + // Orca: Ensure filament_presets has at least one slot per nozzle on FFF printers. + // Called from (load|update)_selections before the parallel project_config arrays + // (filament_colour/colour_type/map) are sized off filament_presets.size(), so a + // short saved filament list doesn't truncate the loaded colors. + void update_filament_count(); + // Load user configuration and store it into the user profiles. // This method is called by the configuration wizard. void load_config_from_wizard(const std::string &name, DynamicPrintConfig config, Semver file_version) From d52d0a082df8ad2cf9e7e3efebfe622aa787404f Mon Sep 17 00:00:00 2001 From: Ioannis Giannakas <59056762+igiannakas@users.noreply.github.com> Date: Sun, 17 May 2026 20:46:08 +0100 Subject: [PATCH 040/117] Option to always travel to wipe tower before Tx commands on multi-toolhead setups using type 2 tower (#13703) * Option to always travel to wipe tower before Tx commands on multi-toolhead setups using type 2 tower --- src/libslic3r/GCode.cpp | 7 ++++++- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/Print.cpp | 1 + src/libslic3r/PrintConfig.cpp | 10 ++++++++++ src/libslic3r/PrintConfig.hpp | 1 + src/slic3r/GUI/Tab.cpp | 7 +++++++ 6 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index a514019412..9dacd594be 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1161,10 +1161,15 @@ static std::vector get_path_of_change_filament(const Print& print) const bool is_ramming = (gcodegen.config().single_extruder_multi_material) || (!gcodegen.config().single_extruder_multi_material && gcodegen.config().filament_multitool_ramming.get_at(tcr.initial_tool)); + // Orca: user-facing override (Printer Settings > Wipe tower > "Tool change on wipe tower"). + // Forces the toolhead to travel over the wipe tower before issuing Tx even on multi-toolhead + // printers without ramming, where Orca would otherwise emit Tx in place (potentially over the part). + const bool tool_change_on_wipe_tower = gcodegen.config().tool_change_on_wipe_tower.value; const bool should_travel_to_tower = !tcr.priming && (tcr.force_travel // wipe tower says so || !needs_toolchange // this is just finishing the tower with no toolchange || will_go_down // Make sure to move to prime tower before moving down - || is_ramming); + || is_ramming + || tool_change_on_wipe_tower); if (should_travel_to_tower || gcodegen.m_need_change_layer_lift_z) { // FIXME: It would be better if the wipe tower set the force_travel flag for all toolchanges, diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index ea9d5a57b1..6780cb6188 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1343,7 +1343,7 @@ static std::vector s_Preset_printer_options { "use_relative_e_distances", "extruder_type", "use_firmware_retraction", "printer_notes", "grab_length", "support_object_skip_flush", "physical_extruder_map", "cooling_tube_retraction", - "cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "wipe_tower_type", "purge_in_prime_tower", "enable_filament_ramming", + "cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "wipe_tower_type", "purge_in_prime_tower", "enable_filament_ramming", "tool_change_on_wipe_tower", "z_offset", "disable_m73", "preferred_orientation", "emit_machine_limits_to_gcode", "pellet_modded_printer", "support_multi_bed_types", "default_bed_type", "bed_mesh_min","bed_mesh_max","bed_mesh_probe_distance", "adaptive_bed_mesh_margin", "enable_long_retraction_when_cut","long_retractions_when_cut","retraction_distances_when_cut", "bed_temperature_formula", "nozzle_flush_dataset" diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 57a17d01e3..d5a4296dcc 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -359,6 +359,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "wipe_tower_filament" || opt_key == "wiping_volumes_extruders" || opt_key == "enable_filament_ramming" + || opt_key == "tool_change_on_wipe_tower" || opt_key == "purge_in_prime_tower" || opt_key == "z_offset" || opt_key == "support_multi_bed_types" diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index ac712818ca..0c0549f5c1 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -5806,6 +5806,16 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(true)); + def = this->add("tool_change_on_wipe_tower", coBool); + def->label = L("Tool change on wipe tower"); + def->tooltip = L("Force the toolhead to travel to the wipe tower before issuing the tool change command (Tx). " + "Only relevant for multi-extruder (multi-toolhead) printers using a Type 2 wipe tower. " + "By default Orca skips the travel on multi-toolhead machines because the firmware handles the head swap, " + "which can result in the Tx command being issued above the printed part. " + "Enable this option if you want the tool change to always be issued above the wipe tower instead."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + def = this->add("wipe_tower_no_sparse_layers", coBool); def->label = L("No sparse layers (beta)"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 416df9c98b..a7f29bda07 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1452,6 +1452,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionEnum, wipe_tower_type)) ((ConfigOptionBool, purge_in_prime_tower)) ((ConfigOptionBool, enable_filament_ramming)) + ((ConfigOptionBool, tool_change_on_wipe_tower)) ((ConfigOptionBool, support_multi_bed_types)) // Small Area Infill Flow Compensation diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index b17be8a76c..9e58d7f9f5 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -4998,6 +4998,7 @@ if (is_marlin_flavor) optgroup->append_single_option_line("wipe_tower_type", "printer_multimaterial_wipe_tower"); optgroup->append_single_option_line("purge_in_prime_tower", "printer_multimaterial_wipe_tower#purge-in-prime-tower"); optgroup->append_single_option_line("enable_filament_ramming", "printer_multimaterial_wipe_tower#enable-filament-ramming"); + optgroup->append_single_option_line("tool_change_on_wipe_tower", "printer_multimaterial_wipe_tower#tool-change-on-wipe-tower"); optgroup = page->new_optgroup(L("Single extruder multi-material parameters"), "param_settings"); @@ -5447,6 +5448,12 @@ void TabPrinter::toggle_options() toggle_option("extruders_count", !bSEMM); toggle_option("manual_filament_change", bSEMM); toggle_option("purge_in_prime_tower", bSEMM && supports_wipe_tower_2); + + // Orca: "Tool change on wipe tower" only makes sense for multi-extruder (multi-toolhead) printers + // using a Type 2 wipe tower. SEMM already always travels to the tower as part of the purge, + // so the option is irrelevant there. + const size_t extruders_count = m_config->option("nozzle_diameter")->size(); + toggle_option("tool_change_on_wipe_tower", !bSEMM && supports_wipe_tower_2 && extruders_count > 1); } wxString extruder_number; long val = 1; From 6cf7db1ded7d3f619c08ace9ce5c9428c5ae2107 Mon Sep 17 00:00:00 2001 From: yw4z Date: Mon, 18 May 2026 00:43:22 +0300 Subject: [PATCH 041/117] Match titlebar font on Linux (#12828) * Update BBLTopbar.cpp * Update BBLTopbar.cpp * Update BBLTopbar.cpp * Update BBLTopbar.cpp --- src/slic3r/GUI/BBLTopbar.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/slic3r/GUI/BBLTopbar.cpp b/src/slic3r/GUI/BBLTopbar.cpp index 9b1c6ab97d..7a5bece0af 100644 --- a/src/slic3r/GUI/BBLTopbar.cpp +++ b/src/slic3r/GUI/BBLTopbar.cpp @@ -58,7 +58,7 @@ CenteredTitle::CenteredTitle(wxWindow* parent) wxRect rect = GetClientRect(); wxString ellipsized = wxControl::Ellipsize(m_title, dc, wxELLIPSIZE_END, wxMax(0, rect.GetWidth() - FromDIP(8))); - int y = rect.y + (rect.height - textHeight) / 2; + int y = rect.y + (rect.height - textHeight) / 2 + 1; int x = rect.x + ((ellipsized != m_title) // is ellipsized ? FromDIP(4) // align to left when clipped : (rect.width - dc.GetTextExtent(m_title).GetWidth()) / 2); // centered when has available space @@ -115,15 +115,14 @@ void BBLTopbarArt::DrawBackground(wxDC& dc, wxWindow* wnd, const wxRect& rect) void BBLTopbarArt::DrawButton(wxDC& dc, wxWindow* wnd, const wxAuiToolBarItem& item, const wxRect& rect) { int textWidth = 0, textHeight = 0; + wxFontMetrics fm; if (m_flags & wxAUI_TB_TEXT) { dc.SetFont(m_font); - int tx, ty; - - dc.GetTextExtent(wxT("ABCDHgj"), &tx, &textHeight); - textWidth = 0; - dc.GetTextExtent(item.GetLabel(), &textWidth, &ty); + fm = dc.GetFontMetrics(); + textHeight = fm.ascent + fm.descent; + dc.GetTextExtent(item.GetLabel(), &textWidth, nullptr); } int bmpX = 0, bmpY = 0; @@ -158,9 +157,7 @@ void BBLTopbarArt::DrawButton(wxDC& dc, wxWindow* wnd, const wxAuiToolBarItem& i (bmpSize.y / 2); textX = bmpX + wnd->FromDIP(3) + bmpSize.x; - textY = rect.y + - (rect.height / 2) - - (textHeight / 2); + textY = bmpY + bmpSize.y / 2 - textHeight / 2 - fm.externalLeading + 1; } @@ -228,7 +225,9 @@ BBLTopbar::BBLTopbar(wxWindow* pwin, wxFrame* parent) void BBLTopbar::Init(wxFrame* parent) { - SetArtProvider(new BBLTopbarArt()); + auto* art = new BBLTopbarArt(); + art->SetFont(Label::Body_12); + SetArtProvider(art); m_frame = parent; m_skip_popup_file_menu = false; m_skip_popup_dropdown_menu = false; @@ -321,6 +320,7 @@ void BBLTopbar::Init(wxFrame* parent) //this->AddStretchSpacer(1); m_title_ctrl = new CenteredTitle(this); + m_title_ctrl->SetFont(Label::Body_12); wxAuiToolBarItem* title_item = this->AddControl(m_title_ctrl, ""); title_item->SetProportion(1); From 1447602d43d550b78135f138a1e35f91e99c2b41 Mon Sep 17 00:00:00 2001 From: r3dbaba <47311570+r3dbaba@users.noreply.github.com> Date: Sun, 17 May 2026 20:54:21 -0400 Subject: [PATCH 042/117] Add ability to assign None/Pan/Rotate to mouse buttons (#10736) * Add logic to handle the left, middle and right buttons being assigned to do nothing, pan or rotate * Add entries for setting the drag actions to preferences * Allow the label text in preferences to wrap * Show mouse mappings in Help -> Keyboard Shortcuts * Re-add preferences in updated layout * Add camera mouse options under camera * Change mouse action strings to use L() for localization * Display "None" when it is selected instead of blank in keyboard shortcuts --------- Co-authored-by: Rob O Co-authored-by: Noisyfox --- src/libslic3r/AppConfig.cpp | 10 ++++- src/slic3r/GUI/GLCanvas3D.cpp | 55 ++++++++++++++++++++++------ src/slic3r/GUI/GLCanvas3D.hpp | 7 +++- src/slic3r/GUI/KBShortcutsDialog.cpp | 14 +++++-- src/slic3r/GUI/Preferences.cpp | 11 ++++-- 5 files changed, 74 insertions(+), 23 deletions(-) diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 33e25f5d9c..b35a754c2b 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -212,8 +212,14 @@ void AppConfig::set_defaults() if (get("camera_navigation_style").empty()) set("camera_navigation_style", "0"); - if (get("swap_mouse_buttons").empty()) - set_bool("swap_mouse_buttons", false); + if (get("left_mouse_drag_action").empty()) + set("left_mouse_drag_action", "2"); + + if (get("middle_mouse_drag_action").empty()) + set("middle_mouse_drag_action", "1"); + + if (get("right_mouse_drag_action").empty()) + set("right_mouse_drag_action", "1"); if (get("reverse_mouse_wheel_zoom").empty()) set_bool("reverse_mouse_wheel_zoom", false); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index b630f46794..3ef5eaf0a9 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4272,7 +4272,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } bool any_gizmo_active = m_gizmos.get_current() != nullptr; - bool swap_mouse_buttons = wxGetApp().app_config->get_bool("swap_mouse_buttons"); + + std::map button_mappings; + button_mappings[MouseButton::Left] = static_cast(std::atoi(wxGetApp().app_config->get("left_mouse_drag_action").c_str())); + button_mappings[MouseButton::Middle] = static_cast(std::atoi(wxGetApp().app_config->get("middle_mouse_drag_action").c_str())); + button_mappings[MouseButton::Right] = static_cast(std::atoi(wxGetApp().app_config->get("right_mouse_drag_action").c_str())); if (m_mouse.drag.move_requires_threshold && m_mouse.is_move_start_threshold_position_2D_defined() && m_mouse.is_move_threshold_met(pos)) { m_mouse.drag.move_requires_threshold = false; @@ -4485,7 +4489,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_dirty = true; } } - else if (evt.Dragging() || is_camera_rotate(evt, swap_mouse_buttons) || is_camera_pan(evt, swap_mouse_buttons)) { + else if (evt.Dragging() || is_camera_rotate(evt, button_mappings) || is_camera_pan(evt, button_mappings)) { m_mouse.dragging = true; if (m_layers_editing.state != LayersEditing::Unknown && layer_editing_object_idx != -1) { @@ -4495,10 +4499,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } } // do not process the dragging if the left mouse was set down in another canvas - else if (is_camera_rotate(evt, swap_mouse_buttons)) { + else if (is_camera_rotate(evt, button_mappings)) { // Orca: Sphere rotation for painting view - // if dragging over blank area with left button or button functions swapped then rotate - if ((any_gizmo_active || swap_mouse_buttons || m_hover_volume_idxs.empty()) && m_mouse.is_start_position_3D_defined()) { + // if dragging over blank area with left button or other button mapped to rotate, then rotate + bool middle_or_right_button_used_as_rotate = (evt.MiddleIsDown() && button_mappings[MouseButton::Middle] == MouseAction::Rotation) || + (evt.RightIsDown() && button_mappings[MouseButton::Right] == MouseAction::Rotation); + if ((any_gizmo_active || middle_or_right_button_used_as_rotate || m_hover_volume_idxs.empty()) && + m_mouse.is_start_position_3D_defined()) { Camera& camera = wxGetApp().plater()->get_camera(); auto mult_pref = wxGetApp().app_config->get("camera_orbit_mult"); const double mult = mult_pref.empty() ? 1.0 : std::stod(mult_pref); @@ -4571,7 +4578,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_camera_movement = true; m_mouse.drag.start_position_3D = Vec3d((double)pos(0), (double)pos(1), 0.0); } - else if (is_camera_pan(evt, swap_mouse_buttons)) { + else if (is_camera_pan(evt, button_mappings)) { // if dragging with right button or if button functions swapped and dragging with left button over blank area then pan if (m_mouse.is_start_position_2D_defined()) { // get point in model space at Z = 0 @@ -4599,10 +4606,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } } else if ((evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) || - (m_camera_movement && !is_camera_rotate(evt, swap_mouse_buttons) && !is_camera_pan(evt, swap_mouse_buttons))) { + (m_camera_movement && !is_camera_rotate(evt, button_mappings) && !is_camera_pan(evt, button_mappings))) { m_mouse.position = pos.cast(); - if (swap_mouse_buttons ? evt.RightUp() : evt.LeftUp()) { + // Check if the button that was released is mapped to rotation + if ((evt.LeftUp() && button_mappings[MouseButton::Left] == MouseAction::Rotation) || + (evt.MiddleUp() && button_mappings[MouseButton::Middle] == MouseAction::Rotation) || + (evt.RightUp() && button_mappings[MouseButton::Right] == MouseAction::Rotation)) { m_rotation_center(0) = m_rotation_center(1) = m_rotation_center(2) = 0.f; } @@ -4795,21 +4805,42 @@ void GLCanvas3D::on_set_focus(wxFocusEvent& evt) m_is_touchpad_navigation = wxGetApp().app_config->get_bool("camera_navigation_style"); } -bool GLCanvas3D::is_camera_rotate(const wxMouseEvent& evt, const bool buttonsSwapped) const +bool GLCanvas3D::clicked_button_matches_action(const wxMouseEvent& evt, const MouseAction action, const std::map& mappings) const +{ + MouseButton clicked = MouseButton::None; + if (evt.LeftIsDown()) { + clicked = MouseButton::Left; + } + if (evt.MiddleIsDown()) { + clicked = MouseButton::Middle; + } + if (evt.RightIsDown()) { + clicked = MouseButton::Right; + } + + auto it = mappings.find(clicked); + if (it == mappings.end()) { + return false; + } + return it->second == action; +} + +bool GLCanvas3D::is_camera_rotate(const wxMouseEvent& evt, const std::map& mappings) const { if (m_is_touchpad_navigation) { return evt.Moving() && evt.AltDown() && !evt.ShiftDown(); } else { - return evt.Dragging() && (buttonsSwapped ? evt.RightIsDown() : evt.LeftIsDown()); + return evt.Dragging() && clicked_button_matches_action(evt, MouseAction::Rotation, mappings); } } -bool GLCanvas3D::is_camera_pan(const wxMouseEvent& evt, const bool buttonsSwapped) const +bool GLCanvas3D::is_camera_pan(const wxMouseEvent& evt, const std::map& mappings) const { if (m_is_touchpad_navigation) { return evt.Moving() && evt.ShiftDown() && !evt.AltDown(); } else { - return evt.Dragging() && (evt.MiddleIsDown() || (buttonsSwapped ? evt.LeftIsDown() : evt.RightIsDown())); + return evt.Dragging() && clicked_button_matches_action(evt, MouseAction::Pan, mappings); + ; } } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index f6c42bdaa9..9e81e5c4f9 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -1038,8 +1038,11 @@ public: void on_set_focus(wxFocusEvent& evt); void force_set_focus(); - bool is_camera_rotate(const wxMouseEvent& evt, const bool buttonsSwapped) const; - bool is_camera_pan(const wxMouseEvent& evt, const bool buttonsSwapped) const; + enum class MouseButton { None, Left, Middle, Right }; + enum class MouseAction { None, Pan, Rotation }; + bool clicked_button_matches_action(const wxMouseEvent& evt, MouseAction action, const std::map& mappings) const; + bool is_camera_rotate(const wxMouseEvent& evt, const std::map& mappings) const; + bool is_camera_pan(const wxMouseEvent& evt, const std::map& mappings) const; Size get_canvas_size() const; Vec2d get_local_mouse_position() const; diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index 2b845385e7..3e0ef64e55 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -207,13 +207,19 @@ void KBShortcutsDialog::fill_shortcuts() { "?", L("Show keyboard shortcuts list") } }; m_full_shortcuts.push_back({{_L("Global shortcuts"), ""}, global_shortcuts}); + - bool swap_mouse_buttons = wxGetApp().app_config->get_bool("swap_mouse_buttons"); + // Retrieve mouse actions from config and map to MouseAction + std::map mouse_actions; + mouse_actions["0"] = L("None"); + mouse_actions["1"] = L("Pan View"); + mouse_actions["2"] = L("Rotate View"); Shortcuts plater_shortcuts = { - { L("Left mouse button"), swap_mouse_buttons ? L("Pan view") : L("Rotate view") }, - { L("Right mouse button"), swap_mouse_buttons ? L("Rotate view") : L("Pan view") }, - { L("Mouse wheel"), L("Zoom view") }, + { L("Left mouse button"), mouse_actions[wxGetApp().app_config->get("left_mouse_drag_action").c_str()]}, + { L("Middle mouse button"), mouse_actions[wxGetApp().app_config->get("middle_mouse_drag_action").c_str()]}, + { L("Right mouse button"), mouse_actions[wxGetApp().app_config->get("right_mouse_drag_action").c_str()]}, + { L("Mouse wheel"), L("Zoom View") }, { "A", L("Arrange all objects") }, { shift + "A", L("Arrange objects on selected plates") }, diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 2641d73f42..206549cf17 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -1500,12 +1500,17 @@ void PreferencesDialog::create_items() auto item_use_free_camera = create_item_checkbox(_L("Use free camera"), _L("If enabled, use free camera. If not enabled, use constrained camera."), "use_free_camera"); g_sizer->Add(item_use_free_camera); - auto swap_pan_rotate = create_item_checkbox(_L("Swap pan and rotate mouse buttons"), _L("If enabled, swaps the left and right mouse buttons pan and rotate functions."), "swap_mouse_buttons"); - g_sizer->Add(swap_pan_rotate); - auto reverse_mouse_zoom = create_item_checkbox(_L("Reverse mouse zoom"), _L("If enabled, reverses the direction of zoom with mouse wheel."), "reverse_mouse_wheel_zoom"); g_sizer->Add(reverse_mouse_zoom); + std::vector ButtonDragActions = {_L("None"), _L("Pan"), _L("Rotate")}; + auto item_left_mouse_drag = create_item_combobox(_L("Left Mouse Drag"), _L("Set the action that dragging the left mouse button should perform."), "left_mouse_drag_action", ButtonDragActions); + g_sizer->Add(item_left_mouse_drag); + auto item_middle_mouse_drag = create_item_combobox(_L("Middle Mouse Drag"), _L("Set the action that dragging the middle mouse button should perform."), "middle_mouse_drag_action", ButtonDragActions); + g_sizer->Add(item_middle_mouse_drag); + auto item_right_mouse_drag = create_item_combobox(_L("Right Mouse Drag"), _L("Set the action that dragging the right mouse button should perform."), "right_mouse_drag_action", ButtonDragActions); + g_sizer->Add(item_right_mouse_drag); + //// CONTROL > Clear my choice on ... g_sizer->Add(create_item_title(_L("Clear my choice on...")), 1, wxEXPAND); From 15451c6c50eb24004852d957b21bf9438315eb1c Mon Sep 17 00:00:00 2001 From: Hanno Witzleb <48943886+Xipit@users.noreply.github.com> Date: Mon, 18 May 2026 06:57:48 +0200 Subject: [PATCH 043/117] Feature: Multiple dovetail cuts (#12318) * adds UI and preview plane for multiple dovetail cuts * adds multiple dovetail cuts to perform_with_groove() * adds ui text info for spacing, adjusts max_val for gap to respect plate dimensions * adds spacing before multpile UI * adjusts wording, adjust gap max by count --------- Co-authored-by: Hanno Witzleb Co-authored-by: SoftFever --- src/libslic3r/CutUtils.cpp | 140 ++++-- src/libslic3r/CutUtils.hpp | 10 +- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 627 ++++++++++++++++++--------- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 14 +- 4 files changed, 546 insertions(+), 245 deletions(-) diff --git a/src/libslic3r/CutUtils.cpp b/src/libslic3r/CutUtils.cpp index 5899313035..f8b1483773 100644 --- a/src/libslic3r/CutUtils.cpp +++ b/src/libslic3r/CutUtils.cpp @@ -554,7 +554,12 @@ const ModelObjectPtrs& Cut::perform_by_contour(const ModelObject* src_object, st } -const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Transform3d& rotation_m, bool keep_as_parts/* = false*/) +const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, + const Transform3d& rotation_m, + const int groove_count, + const float groove_gap, + const float m_radius, + bool keep_as_parts /* = false*/) { ModelObject* cut_mo = m_model.objects.front(); @@ -615,7 +620,7 @@ const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Tran reset_instance_transformation(object, m_instance); }; - // cut by upper plane + // cut by upper plane (+Z) { const Transform3d cut_matrix_upper = translation_transform(rotation_m * (groove_half_depth * Vec3d::UnitZ())) * m_cut_matrix; @@ -623,7 +628,7 @@ const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Tran add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); } - // cut by lower plane + // cut by lower plane (-Z) { const Transform3d cut_matrix_lower = translation_transform(rotation_m * (-groove_half_depth * Vec3d::UnitZ())) * m_cut_matrix; @@ -631,45 +636,92 @@ const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Tran add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); } - // cut middle part with 2 angles and add parts to related upper/lower objects + // Compute same slot outer width used in preview plane + const float groove_width = calculate_groove_width(groove, m_radius); - const double h_side_shift = 0.5 * double(groove.width + groove.depth / tan(groove.flaps_angle)); + ModelObject* groove_object{nullptr}; - // cut by angle1 plane - { - const Transform3d cut_matrix_angle1 = translation_transform(rotation_m * (-h_side_shift * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle)); + // multiple cuts + for (int i = 0; i < groove_count; i++) { + bool is_first_groove = i == 0; + bool is_last_groove = i == groove_count - 1; - cut(tmp_object, cut_matrix_angle1, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); - add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + // Calculate the x-axis offset for this dovetail + float groove_offset_factor_start = -.5 * ((groove_count - 1)); + float groove_offset_factor = groove_offset_factor_start + i; + + float offset_x = groove_offset_factor * (groove_gap + groove_width); + + + tmp_object->clone_for_cut(&groove_object); + for (ModelVolume* volume : tmp_object->volumes) { + ModelVolume* new_vol = groove_object->add_volume(*volume); + new_vol->reset_from_upper(); + } + + // isolate area of current groove + if (!is_first_groove) { + float left_cut_position = (-groove_gap / 2.f) - (groove_width / 2.f) + offset_x; + + const Transform3d cut_matrix_left = translation_transform(rotation_m * (left_cut_position * Vec3d::UnitX())) * + m_cut_matrix * rotation_transform(Vec3d(0, M_PI / 2.0, 0)); + + cut(groove_object, cut_matrix_left, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + } + if (!is_last_groove) { + float right_cut_position = (groove_gap / 2.f) + (groove_width / 2.f) + offset_x; + + const Transform3d cut_matrix_right = translation_transform(rotation_m * (right_cut_position * Vec3d::UnitX())) * + m_cut_matrix * rotation_transform(Vec3d(0, M_PI / 2.0, 0)); + cut(groove_object, cut_matrix_right, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + } + + const Transform3d groove_translation = translation_transform(rotation_m * (offset_x * Vec3d::UnitX())); + // cut middle part with 2 angles and add parts to related upper/lower objects + const double h_side_shift = 0.5 * double(groove.width + groove.depth / tan(groove.flaps_angle)); + + // cut by angle1 plane + { + const Transform3d cut_matrix_angle1 = groove_translation * translation_transform(rotation_m * (-h_side_shift * Vec3d::UnitX())) * + m_cut_matrix * rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle)); + + cut(groove_object, cut_matrix_angle1, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + } + + // cut by angle2 plane + { + const Transform3d cut_matrix_angle2 = groove_translation * translation_transform(rotation_m * (h_side_shift * Vec3d::UnitX())) * + m_cut_matrix * rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle)); + + cut(groove_object, cut_matrix_angle2, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + } + + // apply tolerance to the middle part + { + const double h_groove_shift_tolerance = groove_half_depth - (double)groove.depth_tolerance; + + const Transform3d cut_matrix_lower_tolerance = groove_translation * translation_transform(rotation_m * (-h_groove_shift_tolerance * Vec3d::UnitZ())) * + m_cut_matrix; + cut(groove_object, cut_matrix_lower_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + + const double h_side_shift_tolerance = h_side_shift - 0.5 * double(groove.width_tolerance); + + const Transform3d cut_matrix_angle1_tolerance = groove_translation * translation_transform(rotation_m * (-h_side_shift_tolerance * Vec3d::UnitX())) * + m_cut_matrix * rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle)); + cut(groove_object, cut_matrix_angle1_tolerance, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + + const Transform3d cut_matrix_angle2_tolerance = groove_translation * translation_transform(rotation_m * (h_side_shift_tolerance * Vec3d::UnitX())) * + m_cut_matrix * rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle)); + cut(groove_object, cut_matrix_angle2_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + } + + add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + + groove_object->clear_volumes(); } - // cut by angle2 plane - { - const Transform3d cut_matrix_angle2 = translation_transform(rotation_m * (h_side_shift * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle)); - - cut(tmp_object, cut_matrix_angle2, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); - add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); - } - - // apply tolerance to the middle part - { - const double h_groove_shift_tolerance = groove_half_depth - (double)groove.depth_tolerance; - - const Transform3d cut_matrix_lower_tolerance = translation_transform(rotation_m * (-h_groove_shift_tolerance * Vec3d::UnitZ())) * m_cut_matrix; - cut(tmp_object, cut_matrix_lower_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); - - const double h_side_shift_tolerance = h_side_shift - 0.5 * double(groove.width_tolerance); - - const Transform3d cut_matrix_angle1_tolerance = translation_transform(rotation_m * (-h_side_shift_tolerance * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle)); - cut(tmp_object, cut_matrix_angle1_tolerance, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); - - const Transform3d cut_matrix_angle2_tolerance = translation_transform(rotation_m * (h_side_shift_tolerance * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle)); - cut(tmp_object, cut_matrix_angle2_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); - } - - // this part can be added to the upper object now - add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); - ModelObjectPtrs cut_object_ptrs; if (keep_as_parts) { @@ -713,5 +765,19 @@ const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Tran return m_model.objects; } +float Cut::calculate_groove_width (const Cut::Groove& groove, const float m_radius) +{ + // Compute same slot outer width used in preview plane + const double flap_width = is_approx(groove.flaps_angle, 0.f) ? groove.depth : groove.depth / sin(groove.flaps_angle); + const double total_flap_width = 2.0 * flap_width * cos(groove.flaps_angle); + const double slot_neck_half_width = 0.5f * (groove.width); + const double slot_mouth_half_width = 0.5 * (groove.width + total_flap_width); + const double plane_half_height = 0.5f* (1.5f * (1.5f *m_radius)); + const double flap_taper_offset = plane_half_height * tan(groove.angle); + const double slot_outer_x_max = std::max(slot_mouth_half_width + flap_taper_offset, slot_neck_half_width + flap_taper_offset); + + return float(2.0 * slot_outer_x_max); +} + } // namespace Slic3r diff --git a/src/libslic3r/CutUtils.hpp b/src/libslic3r/CutUtils.hpp index 99cdb9cd65..b7f31d7b9a 100644 --- a/src/libslic3r/CutUtils.hpp +++ b/src/libslic3r/CutUtils.hpp @@ -57,9 +57,15 @@ public: const ModelObjectPtrs& perform_with_plane(); const ModelObjectPtrs& perform_by_contour(const ModelObject* src_object, std::vector parts, int dowels_count); - const ModelObjectPtrs& perform_with_groove(const Groove& groove, const Transform3d& rotation_m, bool keep_as_parts = false); + const ModelObjectPtrs& perform_with_groove(const Groove& groove, + const Transform3d& rotation_m, + const int groove_count, + const float groove_gap, + const float m_radius, + bool keep_as_parts = false); -}; // namespace Cut + static float calculate_groove_width(const Cut::Groove& groove, const float m_radius); + }; // namespace Cut } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index f042656ab7..5fed930321 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -227,6 +227,10 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, {"Groove Angle" , _u8L("Groove Angle")}, {"Cut position" , _u8L("Cut position")}, // ORCA {"Build Volume" , _u8L("Build Volume")}, // ORCA + {"Multiple" , _u8L("Multiple")}, // ORCA + {"Count" , _u8L("Count")}, // ORCA + {"Gap" , _u8L("Gap")}, // ORCA + {"Spacing" , _u8L("Spacing")} // ORCA }; // update_connector_shape(); @@ -543,7 +547,7 @@ bool GLGizmoCut3D::render_double_input(const std::string& label, double& value_i return !is_approx(old_val, value); } -bool GLGizmoCut3D::render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in, float min_val/* = -0.1f*/, float max_tolerance/* = -0.1f*/) +bool GLGizmoCut3D::render_slider_two_input(const std::string& label, float& value_in, float& tolerance_in, float min_val/* = -0.1f*/, float max_tolerance/* = -0.1f*/) { // -------- [ ] -------- [ ] // slider_with + item_in_gap + first_input_width + item_out_gap + slider_with + item_in_gap + second_input_width @@ -613,6 +617,55 @@ bool GLGizmoCut3D::render_slider_double_input(const std::string& label, float& v return !is_approx(old_val, value) || !is_approx(old_tolerance, tolerance); } +bool GLGizmoCut3D::render_slider_input(const std::string& label, float& value_in, float min_val /* = -0.1f*/, float max_val /* = 100.f*/) +{ + // -------- [ ] + // slider_with + input_width + slider_with + item_in_gap + double slider_with = 0.42 * m_editing_window_width; // m_control_width * 0.35; + double item_in_gap = 0.01 * m_editing_window_width; + double input_width = 0.29 * m_editing_window_width; + + constexpr float UndefMinVal = -0.1f; + const float f_mm_to_in = static_cast(GizmoObjectManipulation::mm_to_in); + + ImGui::AlignTextToFramePadding(); + m_imgui->text(label); + ImGui::SameLine(m_label_width); + ImGui::PushItemWidth(slider_with); + + double left_width = m_label_width + slider_with + item_in_gap; + + bool m_imperial_units = false; + + float value = value_in; + float max_value = max_val; + if (m_imperial_units) { + value *= f_mm_to_in; + max_value *= f_mm_to_in; + } + float old_val = value; + + const BoundingBoxf3 bbox = m_bounding_box; + //const float mean_size = float((bbox.size().x() + bbox.size().y() + bbox.size().z())) * (m_imperial_units ? f_mm_to_in : 1.f); + const float min_v = min_val > 0.f ? /*std::min(max_val, mean_size)*/ min_val : 1.f; + + float min_size = value_in < 0.f ? UndefMinVal : min_v; + if (m_imperial_units) { + min_size *= f_mm_to_in; + } + std::string format = value_in < 0.f ? " " : m_imperial_units ? "%.4f " + _u8L("in") : "%.2f " + _u8L("mm"); + + m_imgui->bbl_slider_float_style(("##" + label).c_str(), &value, min_size, max_value, format.c_str()); + + ImGui::SameLine(left_width); + ImGui::PushItemWidth(input_width); + ImGui::BBLDragFloat(("##input_" + label).c_str(), &value, 0.05f, min_size, max_value, format.c_str()); + + value_in = value * float(m_imperial_units ? GizmoObjectManipulation::in_to_mm : 1.0); + + return !is_approx(old_val, value); +} + void GLGizmoCut3D::render_move_center_input(int axis) { m_imgui->text(m_axis_names[axis]+":"); @@ -691,223 +744,299 @@ static double get_grabber_mean_size(const BoundingBoxf3& bb) #endif } +std::vector GLGizmoCut3D::offset_indices(const std::vector& base_indices, size_t vo) +{ + std::vector offset; + int vo_int = static_cast(vo); + for (const auto& tri : base_indices) { + offset.push_back({tri[0] + vo_int, tri[1] + vo_int, tri[2] + vo_int}); + } + return offset; +} + indexed_triangle_set GLGizmoCut3D::its_make_groove_plane() { - // values for calculation + // This function generates a dovetail slot in a wall, viewed from above (top-down). + // The slot has a wide "mouth" (closest to the wall surface) and a narrow "neck" (deepest into the wall). + // The flaps are the tapered sides connecting the mouth to the neck. - const float side_width = is_approx(m_groove.flaps_angle, 0.f) ? m_groove.depth : (m_groove.depth / sin(m_groove.flaps_angle)); - const float flaps_width = 2.f * side_width * cos(m_groove.flaps_angle); + const float flap_taper_width = is_approx(m_groove.flaps_angle, 0.f) ? m_groove.depth : (m_groove.depth / sin(m_groove.flaps_angle)); + const float total_flap_taper_width = 2.f * flap_taper_width * cos(m_groove.flaps_angle); - const float groove_half_width_upper = 0.5f * (m_groove.width); - const float groove_half_width_lower = 0.5f * (m_groove.width + flaps_width); + const float slot_neck_half_width = 0.5f * (m_groove.width); + const float slot_mouth_half_width = 0.5f * (m_groove.width + total_flap_taper_width); const float cut_plane_radius = 1.5f * float(m_radius); const float cut_plane_length = 1.5f * cut_plane_radius; - const float groove_half_depth = 0.5f * m_groove.depth; + const float plane_half_width = 0.5f * cut_plane_radius; // x + const float plane_half_height = 0.5f * cut_plane_length; // y - const float x = 0.5f * cut_plane_radius; - const float y = 0.5f * cut_plane_length; - float z_upper = groove_half_depth; - float z_lower = -groove_half_depth; + const float slot_half_depth = 0.5f * m_groove.depth; // z + float slot_front_z = slot_half_depth; + float slot_back_z = -slot_half_depth; - const float proj = y * tan(m_groove.angle); + const float flap_taper_offset = plane_half_height * tan(m_groove.angle); - float ext_upper_x = groove_half_width_upper + proj; // upper_x extension - float ext_lower_x = groove_half_width_lower + proj; // lower_x extension + float slot_mouth_outer_x = slot_neck_half_width + flap_taper_offset; // upper_x extension + float slot_neck_outer_x = slot_mouth_half_width + flap_taper_offset; // lower_x extension + float slot_outer_x_max = Max(slot_neck_outer_x, slot_mouth_outer_x); // max x extension - float nar_upper_x = groove_half_width_upper - proj; // upper_x narrowing - float nar_lower_x = groove_half_width_lower - proj; // lower_x narrowing + float slot_neck_inner_x = slot_neck_half_width - flap_taper_offset; // upper_x narrowing + float slot_mouth_inner_x = slot_mouth_half_width - flap_taper_offset; // lower_x narrowing - const float cut_plane_thiknes = 0.02f;// 0.02f * (float)get_grabber_mean_size(m_bounding_box); // cut_plane_thiknes + const float wall_thickness = 0.02f; // 0.02f * (float)get_grabber_mean_size(m_bounding_box); // cut_plane_thiknes - // Vertices of the groove used to detection if groove is valid - // They are written as: - // {left_ext_lower, left_nar_lower, left_ext_upper, left_nar_upper, - // right_ext_lower, right_nar_lower, right_ext_upper, right_nar_upper } - { - m_groove_vertices.clear(); - m_groove_vertices.reserve(8); - - m_groove_vertices.emplace_back(Vec3f(-ext_lower_x, -y, z_lower).cast()); - m_groove_vertices.emplace_back(Vec3f(-nar_lower_x, y, z_lower).cast()); - m_groove_vertices.emplace_back(Vec3f(-ext_upper_x, -y, z_upper).cast()); - m_groove_vertices.emplace_back(Vec3f(-nar_upper_x, y, z_upper).cast()); - m_groove_vertices.emplace_back(Vec3f( ext_lower_x, -y, z_lower).cast()); - m_groove_vertices.emplace_back(Vec3f( nar_lower_x, y, z_lower).cast()); - m_groove_vertices.emplace_back(Vec3f( ext_upper_x, -y, z_upper).cast()); - m_groove_vertices.emplace_back(Vec3f( nar_upper_x, y, z_upper).cast()); - } - - // Different cases of groove plane: - - // groove is open - - if (groove_half_width_upper > proj && groove_half_width_lower > proj) { - indexed_triangle_set mesh; - - auto get_vertices = [x, y](float z_upper, float z_lower, float nar_upper_x, float nar_lower_x, float ext_upper_x, float ext_lower_x) { - return std::vector({ - // upper left part vertices - {-x, -y, z_upper}, {-x, y, z_upper}, {-nar_upper_x, y, z_upper}, {-ext_upper_x, -y, z_upper}, - // lower part vertices - {-ext_lower_x, -y, z_lower}, {-nar_lower_x, y, z_lower}, {nar_lower_x, y, z_lower}, {ext_lower_x, -y, z_lower}, - // upper right part vertices - {ext_upper_x, -y, z_upper}, {nar_upper_x, y, z_upper}, {x, y, z_upper}, {x, -y, z_upper} - }); - }; - - mesh.vertices = get_vertices(z_upper, z_lower, nar_upper_x, nar_lower_x, ext_upper_x, ext_lower_x); - mesh.vertices.reserve(2 * mesh.vertices.size()); - - z_upper -= cut_plane_thiknes; - z_lower -= cut_plane_thiknes; - - const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove.flaps_angle); - - nar_upper_x += under_x_shift; - nar_lower_x += under_x_shift; - ext_upper_x += under_x_shift; - ext_lower_x += under_x_shift; - - std::vector vertices = get_vertices(z_upper, z_lower, nar_upper_x, nar_lower_x, ext_upper_x, ext_lower_x); - mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); - - mesh.indices = { - // above view - {5,4,7}, {5,7,6}, // lower part - {3,4,5}, {3,5,2}, // left side - {9,6,8}, {8,6,7}, // right side - {1,0,2}, {2,0,3}, // upper left part - {9,8,10}, {10,8,11}, // upper right part - // under view - {20,21,22}, {20,22,23}, // upper right part - {12,13,14}, {12,14,15}, // upper left part - {18,21,20}, {18,20,19}, // right side - {16,15,14}, {16,14,17}, // left side - {16,17,18}, {16,18,19}, // lower part - // left edge - {1,13,0}, {0,13,12}, - // front edge - {0,12,3}, {3,12,15}, {3,15,4}, {4,15,16}, {4,16,7}, {7,16,19}, {7,19,20}, {7,20,8}, {8,20,11}, {11,20,23}, - // right edge - {11,23,10}, {10,23,22}, - // back edge - {1,13,2}, {2,13,14}, {2,14,17}, {2,17,5}, {5,17,6}, {6,17,18}, {6,18,9}, {9,18,21}, {9,21,10}, {10,21,22} - }; - return mesh; - } - - float cross_pt_upper_y = groove_half_width_upper / tan(m_groove.angle); - - // groove is closed - - if (groove_half_width_upper < proj && groove_half_width_lower < proj) { - float cross_pt_lower_y = groove_half_width_lower / tan(m_groove.angle); - - indexed_triangle_set mesh; - - auto get_vertices = [x, y](float z_upper, float z_lower, float cross_pt_upper_y, float cross_pt_lower_y, float ext_upper_x, float ext_lower_x) { - return std::vector({ - // upper part vertices - {-x, -y, z_upper}, {-x, y, z_upper}, {x, y, z_upper}, {x, -y, z_upper}, - {ext_upper_x, -y, z_upper}, {0.f, cross_pt_upper_y, z_upper}, {-ext_upper_x, -y, z_upper}, - // lower part vertices - {-ext_lower_x, -y, z_lower}, {0.f, cross_pt_lower_y, z_lower}, {ext_lower_x, -y, z_lower} - }); - }; - - mesh.vertices = get_vertices(z_upper, z_lower, cross_pt_upper_y, cross_pt_lower_y, ext_upper_x, ext_lower_x); - mesh.vertices.reserve(2 * mesh.vertices.size()); - - z_upper -= cut_plane_thiknes; - z_lower -= cut_plane_thiknes; - - const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove.flaps_angle); - - cross_pt_upper_y += cut_plane_thiknes; - cross_pt_lower_y += cut_plane_thiknes; - ext_upper_x += under_x_shift; - ext_lower_x += under_x_shift; - - std::vector vertices = get_vertices(z_upper, z_lower, cross_pt_upper_y, cross_pt_lower_y, ext_upper_x, ext_lower_x); - mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); - - mesh.indices = { - // above view - {8,7,9}, // lower part - {5,8,6}, {6,8,7}, // left side - {4,9,8}, {4,8,5}, // right side - {1,0,6}, {1,6,5},{1,5,2}, {2,5,4}, {2,4,3}, // upper part - // under view - {10,11,16}, {16,11,15}, {15,11,12}, {15,12,14}, {14,12,13}, // upper part - {18,15,14}, {14,18,19}, // right side - {17,16,15}, {17,15,18}, // left side - {17,18,19}, // lower part - // left edge - {1,11,0}, {0,11,10}, - // front edge - {0,10,6}, {6,10,16}, {6,17,16}, {6,7,17}, {7,17,19}, {7,19,9}, {4,14,19}, {4,19,9}, {4,14,13}, {4,13,3}, - // right edge - {3,13,12}, {3,12,2}, - // back edge - {2,12,11}, {2,11,1} - }; - - return mesh; - } - - // groove is closed from the roof + int groove_count = this->m_groove_count; + float groove_gap = this->m_groove_gap; indexed_triangle_set mesh; - mesh.vertices = { - // upper part vertices - {-x, -y, z_upper}, {-x, y, z_upper}, {x, y, z_upper}, {x, -y, z_upper}, - {ext_upper_x, -y, z_upper}, {0.f, cross_pt_upper_y, z_upper}, {-ext_upper_x, -y, z_upper}, - // lower part vertices - {-ext_lower_x, -y, z_lower}, {-nar_lower_x, y, z_lower}, {nar_lower_x, y, z_lower}, {ext_lower_x, -y, z_lower} - }; - mesh.vertices.reserve(2 * mesh.vertices.size() + 1); + // handle multiple dovetails/grooves + for (int i = 0; i < groove_count; ++i) { + bool is_first_groove = i == 0; // when a groove is not the last groove, then limit the extent of the right plane so that it doesnt overlap the next groove + bool is_last_groove = i == groove_count - 1; // do the same in reverse if a groove is not the first groove + size_t vertex_index_offset = mesh.vertices.size(); - z_upper -= cut_plane_thiknes; - z_lower -= cut_plane_thiknes; + // Calculate the x-axis offset for this dovetail + float groove_offset_factor_start = -.5 * ((groove_count - 1)); + float groove_offset_factor = groove_offset_factor_start + i; + // Recalculate x with offset (only X-axis offset) + float offset_x = groove_offset_factor * (groove_gap + (2 * slot_outer_x_max)); - const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove.flaps_angle); + // Vertices of the groove used to detection if groove is valid (not used in mesh) + { + m_groove_vertices.clear(); + m_groove_vertices.reserve(8); - nar_lower_x += under_x_shift; - ext_upper_x += under_x_shift; - ext_lower_x += under_x_shift; + m_groove_vertices.emplace_back(Vec3f(-slot_neck_outer_x, -plane_half_height, slot_front_z).cast()); + m_groove_vertices.emplace_back(Vec3f(-slot_mouth_inner_x, plane_half_height, slot_front_z).cast()); + m_groove_vertices.emplace_back(Vec3f(-slot_mouth_outer_x, -plane_half_height, slot_back_z).cast()); + m_groove_vertices.emplace_back(Vec3f(-slot_neck_outer_x, plane_half_height, slot_back_z).cast()); + m_groove_vertices.emplace_back(Vec3f(slot_neck_outer_x, -plane_half_height, slot_front_z).cast()); + m_groove_vertices.emplace_back(Vec3f(slot_mouth_inner_x, plane_half_height, slot_front_z).cast()); + m_groove_vertices.emplace_back(Vec3f(slot_mouth_outer_x, -plane_half_height, slot_back_z).cast()); + m_groove_vertices.emplace_back(Vec3f(slot_neck_outer_x, plane_half_height, slot_back_z).cast()); + } - std::vector vertices = { - // upper part vertices - {-x, -y, z_upper}, {-x, y, z_upper}, {x, y, z_upper}, {x, -y, z_upper}, - {ext_upper_x, -y, z_upper}, {under_x_shift, cross_pt_upper_y, z_upper}, {-under_x_shift, cross_pt_upper_y, z_upper}, {-ext_upper_x, -y, z_upper}, - // lower part vertices - {-ext_lower_x, -y, z_lower}, {-nar_lower_x, y, z_lower}, {nar_lower_x, y, z_lower}, {ext_lower_x, -y, z_lower} - }; - mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); + // ___ + // Case: Groove is open (Top&bottom: __\ /__ ) + if (slot_neck_half_width > flap_taper_offset && slot_mouth_half_width > flap_taper_offset) { + auto get_vertices = [plane_half_width, plane_half_height] + (float slot_front_z, float slot_back_z, float slot_neck_inner_x, float slot_mouth_inner_x, float slot_mouth_outer_x, float slot_neck_outer_x, + float slot_outer_x_max, bool is_first_groove, bool is_last_groove, float groove_gap, float offset_x) + { - mesh.indices = { - // above view - {8,7,10}, {8,10,9}, // lower part - {5,8,7}, {5,7,6}, // left side - {4,10,9}, {4,9,5}, // right side - {1,0,6}, {1,6,5},{1,5,2}, {2,5,4}, {2,4,3}, // upper part - // under view - {11,12,18}, {18,12,17}, {17,12,16}, {16,12,13}, {16,13,15}, {15,13,14}, // upper part - {21,16,15}, {21,15,22}, // right side - {19,18,17}, {19,17,20}, // left side - {19,20,21}, {19,21,22}, // lower part - // left edge - {1,12,11}, {1,11,0}, - // front edge - {0,11,18}, {0,18,6}, {7,19,18}, {7,18,6}, {7,19,22}, {7,22,10}, {10,22,15}, {10,15,4}, {4,15,14}, {4,14,3}, - // right edge - {3,14,13}, {3,14,2}, - // back edge - {2,13,12}, {2,12,1}, {5,16,21}, {5,21,9}, {9,21,20}, {9,20,8}, {5,17,20}, {5,20,8} - }; + return std::vector({ + // front left part vertices + {is_first_groove ? -plane_half_width + offset_x : -(groove_gap / 2.f) - slot_outer_x_max + offset_x, -plane_half_height, slot_front_z}, // *__/ \__ + {is_first_groove ? -plane_half_width + offset_x : -(groove_gap / 2.f) - slot_outer_x_max + offset_x, plane_half_height, slot_front_z}, // *__/ \__ + {-slot_neck_inner_x + offset_x, plane_half_height, slot_front_z}, // __*/ \__ + {-slot_mouth_outer_x + offset_x, -plane_half_height, slot_front_z}, // __/* \__ + // back part vertices + {-slot_neck_outer_x + offset_x, -plane_half_height, slot_back_z}, // __*/ \__ + {-slot_mouth_inner_x + offset_x, plane_half_height, slot_back_z}, // __/* \__ + {slot_mouth_inner_x + offset_x, plane_half_height, slot_back_z}, // __/ *\__ + {slot_neck_outer_x + offset_x, -plane_half_height, slot_back_z}, // __/ \*__ + // front right part vertices + {slot_mouth_outer_x + offset_x, -plane_half_height, slot_front_z}, // __/ \*__ + {slot_neck_inner_x + offset_x, plane_half_height, slot_front_z}, // __/ *\__ + {is_last_groove ? plane_half_width + offset_x : (groove_gap / 2.f) + slot_outer_x_max + offset_x, plane_half_height, slot_front_z}, // __/ \__* + {is_last_groove ? plane_half_width + offset_x : (groove_gap / 2.f) + slot_outer_x_max + offset_x, -plane_half_height, slot_front_z}}); // __/ \__* + }; + + std::vector vertices = get_vertices(slot_front_z, slot_back_z, slot_neck_inner_x, slot_mouth_inner_x, slot_mouth_outer_x, slot_neck_outer_x, slot_outer_x_max, + is_first_groove, is_last_groove, groove_gap, offset_x); + mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); + + // Back face + slot_front_z -= wall_thickness; + slot_back_z -= wall_thickness; + + const float slot_back_face_x_offset = wall_thickness / tan(0.5f * m_groove.flaps_angle); + slot_neck_inner_x += slot_back_face_x_offset; + slot_mouth_inner_x += slot_back_face_x_offset; + slot_mouth_outer_x += slot_back_face_x_offset; + slot_neck_outer_x += slot_back_face_x_offset; + + vertices = get_vertices(slot_front_z, slot_back_z, slot_neck_inner_x, slot_mouth_inner_x, slot_mouth_outer_x, slot_neck_outer_x, + slot_outer_x_max, is_first_groove, + is_last_groove, groove_gap, offset_x); + mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); + + std::vector base_indices; + + base_indices = { + // above view + {5,4,7}, {5,7,6}, // lower part + {3,4,5}, {3,5,2}, // left side + {9,6,8}, {8,6,7}, // right side + {1,0,2}, {2,0,3}, // upper left part + {9,8,10}, {10,8,11}, // upper right part + // under view + {20,21,22}, {20,22,23}, // upper right part + {12,13,14}, {12,14,15}, // upper left part + {18,21,20}, {18,20,19}, // right side + {16,15,14}, {16,14,17}, // left side + {16,17,18}, {16,18,19}, // lower part + // left edge + {1,13,0}, {0,13,12}, + // front edge + {0,12,3}, {3,12,15}, {3,15,4}, {4,15,16}, {4,16,7}, {7,16,19}, {7,19,20}, {7,20,8}, {8,20,11}, {11,20,23}, + // right edge + {11,23,10}, {10,23,22}, + // back edge + {1,13,2}, {2,13,14}, {2,14,17}, {2,17,5}, {5,17,6}, {6,17,18}, {6,18,9}, {9,18,21}, {9,21,10}, {10,21,22} + }; + + std::vector indices = offset_indices(base_indices, vertex_index_offset); + mesh.indices.insert(mesh.indices.end(), indices.begin(), indices.end()); + } + + // __ + // CASE: Groove is closed (Top&Bottom: ___/ \___) + else if (slot_neck_half_width < flap_taper_offset && slot_mouth_half_width < flap_taper_offset) { + float slot_neck_apex_y = slot_neck_half_width / tan(m_groove.angle); + float slot_mouth_apex_y = slot_mouth_half_width / tan(m_groove.angle); + + auto get_vertices = [plane_half_width, plane_half_height] + (float slot_front_z, float slot_back_z, float slot_neck_apex_y, float slot_mouth_apex_y, float slot_mouth_outer_x, float slot_neck_outer_x, + float slot_outer_x_max, bool is_first_groove, bool is_last_groove, float groove_gap, float offset_x) + { + return std::vector({ + // front part vertices + {is_first_groove ? -plane_half_width + offset_x : -(groove_gap / 2.f) - slot_outer_x_max + offset_x, -plane_half_height, slot_front_z}, // *__/\__ + {is_first_groove ? -plane_half_width + offset_x : -(groove_gap / 2.f) - slot_outer_x_max + offset_x, plane_half_height, slot_front_z}, // *__/\__ + {is_last_groove ? plane_half_width + offset_x : (groove_gap / 2.f) + slot_outer_x_max + offset_x, plane_half_height, slot_front_z}, // __/\__* + {is_last_groove ? plane_half_width + offset_x : (groove_gap / 2.f) + slot_outer_x_max + offset_x, -plane_half_height, slot_front_z}, // __/\__* + {slot_mouth_outer_x + offset_x, -plane_half_height, slot_front_z}, // __*/\__ + {offset_x, slot_neck_apex_y, slot_front_z}, // __/*\__ + {-slot_mouth_outer_x + offset_x, -plane_half_height, slot_front_z}, // __/\*__ + // back part vertices + {-slot_neck_outer_x + offset_x, -plane_half_height, slot_back_z}, // __*/\__ + {offset_x, slot_mouth_apex_y, slot_back_z}, // __/*\__ + {slot_neck_outer_x + offset_x, -plane_half_height, slot_back_z}}); // __/\*__ + }; + + std::vector vertices = get_vertices(slot_front_z, slot_back_z, slot_neck_apex_y, slot_mouth_apex_y, + slot_mouth_outer_x, slot_neck_outer_x, slot_outer_x_max, + is_first_groove, is_last_groove, groove_gap, offset_x); + mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); + + // Back face + slot_front_z -= wall_thickness; + slot_back_z -= wall_thickness; + slot_neck_apex_y += wall_thickness; + slot_mouth_apex_y += wall_thickness; + + const float slot_back_face_x_offset = wall_thickness / tan(0.5f * m_groove.flaps_angle); + slot_mouth_outer_x += slot_back_face_x_offset; + slot_neck_outer_x += slot_back_face_x_offset; + + vertices = get_vertices(slot_front_z, slot_back_z, slot_neck_apex_y, slot_mouth_apex_y, slot_mouth_outer_x, slot_neck_outer_x, + slot_outer_x_max, is_first_groove, + is_last_groove, groove_gap, offset_x); + mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); + + std::vector base_indices = { + // above view + {8,7,9}, // lower part + {5,8,6}, {6,8,7}, // left side + {4,9,8}, {4,8,5}, // right side + {1,0,6}, {1,6,5},{1,5,2}, + {2,5,4}, {2,4,3}, // upper part + // under view + {10,11,16}, {16,11,15}, + {15,11,12}, {15,12,14}, {14,12,13}, // upper part + {18,15,14}, {14,18,19}, // right side + {17,16,15}, {17,15,18}, // left side + {17,18,19}, // lower part + // left edge + {1,11,0}, {0,11,10}, + // front edge + {0,10,6}, {6,10,16}, {6,17,16}, {6,7,17}, {7,17,19}, {7,19,9}, {4,14,19}, {4,19,9}, {4,14,13}, {4,13,3}, + // right edge + {3,13,12}, {3,12,2}, + // back edge + {2,12,11}, {2,11,1} + }; + std::vector indices = offset_indices(base_indices, vertex_index_offset); + + mesh.indices.insert(mesh.indices.end(), indices.begin(), indices.end()); + } + + // Case: Groove is closed from the roof (TOP&Bottom: __/\__ ) + else { + float slot_neck_apex_y = slot_neck_half_width / tan(m_groove.angle); + + std::vector vertices = { + // front part vertices + {is_first_groove ? -plane_half_width + offset_x : -(groove_gap / 2.f) - slot_outer_x_max + offset_x, -plane_half_height, slot_front_z}, + {is_first_groove ? -plane_half_width + offset_x : -(groove_gap / 2.f) - slot_outer_x_max + offset_x, plane_half_height, slot_front_z}, + {is_last_groove ? plane_half_width + offset_x : (groove_gap / 2.f) + slot_outer_x_max + offset_x, plane_half_height, slot_front_z}, + {is_last_groove ? plane_half_width + offset_x : (groove_gap / 2.f) + slot_outer_x_max + offset_x, -plane_half_height, slot_front_z}, + {slot_mouth_outer_x + offset_x, -plane_half_height, slot_front_z}, + {offset_x, slot_neck_apex_y, slot_front_z}, + {-slot_mouth_outer_x + offset_x, -plane_half_height, slot_front_z}, + // back part vertices + {-slot_neck_outer_x + offset_x, -plane_half_height, slot_back_z}, + {-slot_mouth_inner_x + offset_x, plane_half_height, slot_back_z}, + {slot_mouth_inner_x + offset_x, plane_half_height, slot_back_z}, + {slot_neck_outer_x + offset_x, -plane_half_height, slot_back_z}}; + mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); + + // Back face + slot_front_z -= wall_thickness; + slot_back_z -= wall_thickness; + + const float slot_back_face_x_offset = wall_thickness / tan(0.5f * m_groove.flaps_angle); + slot_mouth_inner_x += slot_back_face_x_offset; + slot_mouth_outer_x += slot_back_face_x_offset; + slot_neck_outer_x += slot_back_face_x_offset; + + vertices = { + // upper part vertices + {is_first_groove ? -plane_half_width + offset_x : -(groove_gap / 2.f) - slot_outer_x_max + offset_x, -plane_half_height, slot_front_z}, + {is_first_groove ? -plane_half_width + offset_x : -(groove_gap / 2.f) - slot_outer_x_max + offset_x, plane_half_height, slot_front_z}, + {is_last_groove ? plane_half_width + offset_x : (groove_gap / 2.f) + slot_outer_x_max + offset_x, plane_half_height, slot_front_z}, + {is_last_groove ? plane_half_width + offset_x : (groove_gap / 2.f) + slot_outer_x_max + offset_x, -plane_half_height, slot_front_z}, + {slot_mouth_outer_x + offset_x, -plane_half_height, slot_front_z}, + {slot_back_face_x_offset + offset_x, slot_neck_apex_y, slot_front_z}, + {-slot_back_face_x_offset + offset_x, slot_neck_apex_y, slot_front_z}, + {-slot_mouth_outer_x + offset_x, -plane_half_height, slot_front_z}, + // lower part vertices + {-slot_neck_outer_x + offset_x, -plane_half_height, slot_back_z}, + {-slot_mouth_inner_x + offset_x, plane_half_height, slot_back_z}, + {slot_mouth_inner_x + offset_x, plane_half_height, slot_back_z}, + {slot_neck_outer_x + offset_x, -plane_half_height, slot_back_z}}; + mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); + + // Indices for this dovetail + std::vector base_indices = { + // above view + {8,7,10}, {8,10,9}, // lower part + {5,8,7}, {5,7,6}, // left side + {4,10,9}, {4,9,5}, // right side + {1,0,6}, {1,6,5},{1,5,2}, {2,5,4}, {2,4,3}, // upper part + // under view + {11,12,18}, {18,12,17}, {17,12,16}, {16,12,13}, {16,13,15}, {15,13,14}, // upper part + {21,16,15}, {21,15,22}, // right side + {19,18,17}, {19,17,20}, // left side + {19,20,21}, {19,21,22}, // lower part + // left edge + {1,12,11}, {1,11,0}, + // front edge + {0,11,18}, {0,18,6}, {7,19,18}, {7,18,6}, {7,19,22}, {7,22,10}, {10,22,15}, {10,15,4}, {4,15,14}, {4,14,3}, + // right edge + {3,14,13}, {3,14,2}, + // back edge + {2,13,12}, {2,12,1}, {5,16,21}, {5,21,9}, {9,21,20}, {9,20,8}, {5,17,20}, {5,20,8} + }; + std::vector indices = offset_indices(base_indices, vertex_index_offset); + + mesh.indices.insert(mesh.indices.end(), indices.begin(), indices.end()); + } + } return mesh; } @@ -2290,7 +2419,7 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors, flo m_imgui->disabled_end(); const float depth_min_value = m_connector_type == CutConnectorType::Snap ? m_connector_size : -0.1f; - if (render_slider_double_input(m_labels_map["Depth"], m_connector_depth_ratio, m_connector_depth_ratio_tolerance, depth_min_value)) + if (render_slider_two_input(m_labels_map["Depth"], m_connector_depth_ratio, m_connector_depth_ratio_tolerance, depth_min_value)) apply_selected_connectors([this, &connectors](size_t idx) { if (m_connector_depth_ratio > 0) connectors[idx].height = m_connector_depth_ratio; @@ -2298,7 +2427,7 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors, flo connectors[idx].height_tolerance = m_connector_depth_ratio_tolerance; }); - if (render_slider_double_input(m_labels_map["Size"], m_connector_size, m_connector_size_tolerance)) + if (render_slider_two_input(m_labels_map["Size"], m_connector_size, m_connector_size_tolerance)) apply_selected_connectors([this, &connectors](size_t idx) { if (m_connector_size > 0) connectors[idx].radius = 0.5f * m_connector_size; @@ -2430,7 +2559,7 @@ void GLGizmoCut3D::process_contours() if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { if (has_valid_groove()) { Cut cut(model_objects[object_idx], instance_idx, get_cut_matrix(selection)); - const ModelObjectPtrs& new_objects = cut.perform_with_groove(m_groove, m_rotation_m, true); + const ModelObjectPtrs& new_objects = cut.perform_with_groove(m_groove, m_rotation_m, m_groove_count, m_groove_gap, m_radius, true); if (!new_objects.empty()) m_part_selection = PartSelection(new_objects.front(), instance_idx); } @@ -2485,13 +2614,13 @@ void GLGizmoCut3D::render_color_marker(float size, const ImU32& color) ImGui::SameLine(); } -void GLGizmoCut3D::render_groove_float_input(const std::string& label, float& in_val, const float& init_val, float& in_tolerance) +void GLGizmoCut3D::render_groove_two_float_input(const std::string& label, float& in_val, const float& init_val, float& in_tolerance) { bool is_changed{false}; float val = in_val; float tolerance = in_tolerance; - if (render_slider_double_input(label, val, tolerance, -0.1f, std::min(0.3f*in_val, 1.5f))) { + if (render_slider_two_input(label, val, tolerance, -0.1f, std::min(0.3f*in_val, 1.5f))) { if (m_imgui->get_last_slider_status().can_take_snapshot) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), GUI::format("%1%: %2%", _u8L("Groove change"), label), UndoRedo::SnapshotType::GizmoAction); m_imgui->get_last_slider_status().invalidate_snapshot(); @@ -2525,6 +2654,82 @@ void GLGizmoCut3D::render_groove_float_input(const std::string& label, float& in } } +void GLGizmoCut3D::render_groove_float_input(const std::string& label, float& in_val, const float& init_val, const bool disabled) +{ + if (disabled) + m_imgui->disabled_begin(true); + + bool is_changed{false}; + Vec2d plate_size = wxGetApp().plater()->get_partplate_list().get_plate(0)->get_size(); + float max_val = std::max(plate_size.x(), plate_size.y()) / (std::max(m_groove_count - 1, 1)); + + float val = in_val; + if (render_slider_input(label, val, -0.1f, max_val)) { + if (m_imgui->get_last_slider_status().can_take_snapshot) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), GUI::format("%1%: %2%", _u8L("Groove change"), label), + UndoRedo::SnapshotType::GizmoAction); + m_imgui->get_last_slider_status().invalidate_snapshot(); + m_groove_editing = true; + } + in_val = val; + is_changed = true; + } + + ImGui::SameLine(); + + if (!disabled) m_imgui->disabled_begin(is_approx(in_val, init_val) ); + const std::string act_name = _u8L("Reset"); + if (render_reset_button("##groove_" + label + act_name, act_name)) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), GUI::format("%1%: %2%", act_name, label), UndoRedo::SnapshotType::GizmoAction); + in_val = init_val; + is_changed = true; + } + if (!disabled) m_imgui->disabled_end(); + + if (is_changed) { + update_plane_model(); + reset_cut_by_contours(); + } + + if (m_is_slider_editing_done) { + m_groove_editing = false; + reset_cut_by_contours(); + } + + if (disabled) + m_imgui->disabled_end(); +} + +void GLGizmoCut3D::render_groove_int_input(const std::string& label, int& in_val, const int& init_val, int min_val, int max_val) +{ + bool is_changed{false}; + + m_imgui->text(label); + ImGui::AlignTextToFramePadding(); + ImGui::SameLine(m_label_width); + ImGui::PushItemWidth(m_control_width); + if (ImGui::InputInt("##int_input", &in_val, 1, 5)) { + in_val = std::clamp(in_val, min_val, max_val); + + is_changed = true; + } + + ImGui::SameLine(); + m_imgui->disabled_begin(in_val == init_val); + const std::string act_name = _u8L("Reset"); + if (render_reset_button("##int_" + label + act_name, act_name)) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), GUI::format("%1%: %2%", act_name, label), UndoRedo::SnapshotType::GizmoAction); + in_val = init_val; + is_changed = true; + } + m_imgui->disabled_end(); + + if (is_changed) { + update_plane_model(); + reset_cut_by_contours(); + } +} + bool GLGizmoCut3D::render_angle_input(const std::string& label, float& in_val, const float& init_val, float min_val, float max_val) { // -------- [ ] @@ -2693,10 +2898,24 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors, floa m_is_slider_editing_done = false; ImGui::Separator(); m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, m_labels_map["Groove"] + ": "); - render_groove_float_input(m_labels_map["Depth"], m_groove.depth, m_groove.depth_init, m_groove.depth_tolerance); - render_groove_float_input(m_labels_map["Width"], m_groove.width, m_groove.width_init, m_groove.width_tolerance); + render_groove_two_float_input(m_labels_map["Depth"], m_groove.depth, m_groove.depth_init, m_groove.depth_tolerance); + render_groove_two_float_input(m_labels_map["Width"], m_groove.width, m_groove.width_init, m_groove.width_tolerance); render_groove_angle_input(m_labels_map["Flap Angle"], m_groove.flaps_angle, m_groove.flaps_angle_init, 30.f, 120.f); render_groove_angle_input(m_labels_map["Groove Angle"], m_groove.angle, m_groove.angle_init, 0.f, 15.f); + + ImGui::Spacing(); + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, m_labels_map["Multiple"] + ": "); + render_groove_int_input(m_labels_map["Count"], m_groove_count, m_groove_count_init , 1, 100); + render_groove_float_input(m_labels_map["Gap"], m_groove_gap, m_groove_gap_init, m_groove_count == 1); + + m_imgui->disabled_begin(true); + const float groove_width = Cut::calculate_groove_width(m_groove, m_radius); + m_imgui->text(m_labels_map["Spacing"]); + ImGui::SameLine(m_label_width); + std::ostringstream oss; + oss << std::fixed << std::setprecision(2) << (m_groove_gap + groove_width); + m_imgui->text(oss.str() + "mm"); + m_imgui->disabled_end(); } ImGui::Separator(); @@ -3338,7 +3557,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) Cut cut(cut_mo, instance_idx, get_cut_matrix(selection), attributes); const ModelObjectPtrs& new_objects = cut_by_contour ? cut.perform_by_contour(mo, m_part_selection.get_cut_parts(), dowels_count): - cut_with_groove ? cut.perform_with_groove(m_groove, m_rotation_m) : + cut_with_groove ? cut.perform_with_groove(m_groove, m_rotation_m, m_groove_count, m_groove_gap, m_radius) : cut.perform_with_plane(); // fix_non_manifold_edges diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 73838f43c3..e6cbb7d0ef 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -124,6 +124,12 @@ class GLGizmoCut3D : public GLGizmoBase bool m_is_slider_editing_done { false }; + // Multiple Dovetail cuts + int m_groove_count_init { 1 }; + int m_groove_count { 1 }; + float m_groove_gap { 10.f }; // distance between multiple dovetail cuts + float m_groove_gap_init { 10.f }; + // Input params for cut with snaps float m_snap_bulge_proportion{ 0.15f }; float m_snap_space_proportion{ 0.3f }; @@ -301,8 +307,10 @@ protected: void add_horizontal_scaled_interval(float interval); void add_horizontal_shift(float shift); void render_color_marker(float size, const ImU32& color); - void render_groove_float_input(const std::string &label, float &in_val, const float &init_val, float &in_tolerance); + void render_groove_two_float_input(const std::string &label, float &in_val, const float &init_val, float &in_tolerance); + void render_groove_float_input(const std::string &label, float &in_val, const float &init_val, const bool disabled); void render_groove_angle_input(const std::string &label, float &in_val, const float &init_val, float min_val, float max_val); + void render_groove_int_input(const std::string& label, int& in_val, const int& init_val, int min_val, int max_val); bool render_angle_input(const std::string& label, float& in_val, const float& init_val, float min_val, float max_val); void render_snap_specific_input(const std::string& label, const wxString& tooltip, float& in_val, const float& init_val, const float min_val, const float max_val); void render_cut_plane_input_window(CutConnectors &connectors, float x, float y, float bottom_limit); @@ -338,7 +346,8 @@ private: void switch_to_mode(size_t new_mode); bool render_cut_mode_combo(); bool render_double_input(const std::string& label, double& value_in); - bool render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in, float min_val = -0.1f, float max_tolerance = -0.1f); + bool render_slider_two_input(const std::string& label, float& value_in, float& tolerance_in, float min_val = -0.1f, float max_tolerance = -0.1f); + bool render_slider_input(const std::string& label, float& value_in, float min_val = -0.1f, float max_val = 100.f); void render_move_center_input(int axis); void render_connect_mode_radio_button(CutConnectorMode mode); bool render_reset_button(const std::string& label_id, const std::string& tooltip) const; @@ -378,6 +387,7 @@ private: void toggle_model_objects_visibility(); + std::vector offset_indices(const std::vector& base_indices, size_t vo); indexed_triangle_set its_make_groove_plane(); indexed_triangle_set get_connector_mesh(CutConnectorAttributes connector_attributes); From 3ca7c4b75220f00a12f34d78153210951833ce0b Mon Sep 17 00:00:00 2001 From: Hanno Witzleb <48943886+Xipit@users.noreply.github.com> Date: Mon, 18 May 2026 06:58:58 +0200 Subject: [PATCH 044/117] Unify Tool UI Footer (#12850) * unify footer adds "Done" Buttons, standardize "Reset" Buttons, adds disabled logic to "Reset" Buttons * adds ImGuiWrapper::COL_WARNING = ColorRGB:Warning, replaces ColorRGB::Warning * consistently display warnings, moves assermbly warning after footer, make cut warnings warning color * adds separator above cut warnings * adds tooltip entry in move, rotate & scale for auto-drop * use wrapper tooltip in wrapper->button for consistent text color * restructures brim ears button, renames "Reset" button in painting tools to "Erase All", small styling fixes * small adjustments, moves "Edge detection" checkbox in painting tool underneath, moves "Section View" in Brim ears tool after buttons, moves warnings in Assemble tool under footer * small brim ear tool structure changes, adds separator between "Head Diameter" and "Max angle", adds tooltip to "Auto-generate" button, rewords shortcut descriptions to match other tools --------- Co-authored-by: Hanno Witzleb --- src/slic3r/GUI/GLCanvas3D.cpp | 23 ++- src/slic3r/GUI/GLCanvas3D.hpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoAssembly.cpp | 49 +++-- src/slic3r/GUI/Gizmos/GLGizmoBrimEars.cpp | 176 +++++++++++------- src/slic3r/GUI/Gizmos/GLGizmoBrimEars.hpp | 1 + src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 98 +++++----- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 101 +++++++--- src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 3 + src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 65 ++++--- src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp | 24 ++- src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp | 23 ++- .../GUI/Gizmos/GLGizmoMmuSegmentation.cpp | 57 +++--- src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp | 20 +- src/slic3r/GUI/Gizmos/GLGizmoUtils.cpp | 115 +++++++++--- src/slic3r/GUI/Gizmos/GLGizmoUtils.hpp | 13 +- .../GUI/Gizmos/GizmoObjectManipulation.cpp | 54 +++++- src/slic3r/GUI/ImGuiWrapper.cpp | 11 +- src/slic3r/GUI/ImGuiWrapper.hpp | 1 + 18 files changed, 549 insertions(+), 289 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 3ef5eaf0a9..011231a8f8 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -226,7 +226,7 @@ bool GLCanvas3D::LayersEditing::is_allowed() const float GLCanvas3D::LayersEditing::s_overlay_window_width; -void GLCanvas3D::LayersEditing::render_variable_layer_height_dialog(const GLCanvas3D& canvas) { +void GLCanvas3D::LayersEditing::render_variable_layer_height_dialog(GLCanvas3D& canvas) { if (!m_enabled) return; @@ -336,8 +336,23 @@ void GLCanvas3D::LayersEditing::render_variable_layer_height_dialog(const GLCanv GLGizmoUtils::render_tooltip_button(&imgui, canvas, shortcuts, x, y); ImGui::SameLine(); - if (imgui.button(_L("Reset"))) - wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), SimpleEvent(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE)); + imgui.disabled_begin(check_object_layers_fixed(*m_slicing_parameters, m_layer_height_profile)); + if (imgui.button(_L("Reset"))) { + wxPostEvent((wxEvtHandler*) canvas.get_wxglcanvas(), SimpleEvent(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE)); + } + imgui.disabled_end(); + + ImGui::SameLine(); + GLGizmoUtils::begin_right_aligned_buttons({_L("Done")}); + if (imgui.button(_L("Done"))) { + m_enabled = false; + + GLToolbarItem* item = canvas.m_main_toolbar.get_item("layersediting"); + item->set_state(GLToolbarItem::Normal); + + canvas.set_as_dirty(); + canvas.request_extra_frame(); + } GLCanvas3D::LayersEditing::s_overlay_window_width = ImGui::GetWindowSize().x; imgui.end(); @@ -345,7 +360,7 @@ void GLCanvas3D::LayersEditing::render_variable_layer_height_dialog(const GLCanv imgui.pop_toolbar_style(); } -void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) +void GLCanvas3D::LayersEditing::render_overlay(GLCanvas3D& canvas) { render_variable_layer_height_dialog(canvas); render_active_object_annotations(canvas); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 9e81e5c4f9..6c3e4573e1 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -290,8 +290,8 @@ class GLCanvas3D bool is_enabled() const { return m_enabled; } void set_enabled(bool enabled) { m_enabled = is_allowed() && enabled; } - void render_variable_layer_height_dialog(const GLCanvas3D& canvas); - void render_overlay(const GLCanvas3D& canvas); + void render_variable_layer_height_dialog(GLCanvas3D& canvas); + void render_overlay(GLCanvas3D& canvas); void render_volumes(const GLCanvas3D& canvas, const GLVolumeCollection& volumes); void adjust_layer_height_profile(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoAssembly.cpp b/src/slic3r/GUI/Gizmos/GLGizmoAssembly.cpp index f6ce2533f3..2413ddc38d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoAssembly.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoAssembly.cpp @@ -104,20 +104,24 @@ void GLGizmoAssembly::on_render_input_window(float x, float y, float bottom_limi } show_selection_ui(); show_face_face_assembly_common(); + ImGui::Separator(); show_face_face_assembly_senior(); show_distance_xyz_ui(); - render_input_window_warning(m_same_model_object); + ImGui::Separator(); - - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); - - GLGizmoUtils::render_tooltip_button(m_imgui, m_parent, m_shortcuts, x, y); - float f_scale =m_parent.get_gizmos_manager().get_layout_scale(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); - ImGui::PopStyleVar(2); + GLGizmoUtils::render_tooltip_button(m_imgui, m_parent, m_shortcuts, x, y); + + ImGui::SameLine(); + GLGizmoUtils::begin_right_aligned_buttons({ _L("Done") }); + if (m_imgui->button(_L("Done"))) { + m_parent.reset_all_gizmos(); + } + + render_input_window_warning(m_same_model_object); if (last_feature != m_curr_feature || last_mode != m_mode || last_selected_features != m_selected_features) { // the dialog may have changed its size, ask for an extra frame to render it properly @@ -127,21 +131,40 @@ void GLGizmoAssembly::on_render_input_window(float x, float y, float bottom_limi m_imgui->set_requires_extra_frame(); } m_last_active_item_imgui = m_current_active_imgui_id; + GizmoImguiEnd(); // Orca + ImGui::PopStyleVar(1); // ImGuiStyleVar_FramePadding ImGuiWrapper::pop_toolbar_style(); } void GLGizmoAssembly::render_input_window_warning(bool same_model_object) { - if (wxGetApp().plater()->canvas3D()->get_canvas_type() == GLCanvas3D::ECanvasType::CanvasView3D) { - if (m_hit_different_volumes.size() == 2) { - if (same_model_object == false) { - m_imgui->warning_text(_L("Warning") + ": " + - _L("It is recommended to assemble the objects first,\nbecause the objects is restriced to bed \nand only parts can be lifted.")); - } + const bool same_mesh_warning = m_hit_different_volumes.size() == 1; + const bool wrong_feature_warning = m_selected_wrong_feature_waring_tip; + const bool not_assembled_warning = m_hit_different_volumes.size() == 2 && same_model_object == false && + wxGetApp().plater()->canvas3D()->get_canvas_type() == GLCanvas3D::ECanvasType::CanvasView3D; + + if (same_mesh_warning || not_assembled_warning || wrong_feature_warning) { + ImGui::Separator(); + } + + if (same_mesh_warning) { + m_imgui->warning_text(_L("Warning: please select two different meshes.")); + } + if (wrong_feature_warning) { + if (m_assembly_mode == AssemblyMode::FACE_FACE) { + m_imgui->warning_text(_L("Warning: please select Plane's feature.")); + } else if (m_assembly_mode == AssemblyMode::POINT_POINT) { + m_imgui->warning_text(_L("Warning: please select Point's or Circle's feature.")); } } + if (not_assembled_warning) { + m_imgui->warning_text( + _L("Warning") + ": " + + _L("It is recommended to assemble the objects first,\nbecause the objects is restriced to bed \nand only parts can be lifted.") + ); + } } bool GLGizmoAssembly::render_assembly_mode_combo(double label_width, float item_width) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.cpp index 682be933ad..45aaf37a86 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.cpp @@ -48,19 +48,22 @@ bool GLGizmoBrimEars::on_init() const wxString ctrl = GUI::shortkey_ctrl_prefix(); const wxString alt = GUI::shortkey_alt_prefix(); - m_desc["head_diameter"] = _L("Head diameter"); - m_desc["max_angle"] = _L("Max angle"); - m_desc["detection_radius"] = _L("Detection radius"); - m_desc["remove_selected"] = _L("Remove selected points"); - m_desc["remove_all"] = _L("Remove all"); - m_desc["auto_generate"] = _L("Auto-generate points"); - m_desc["section_view"] = _L("Section view"); + m_desc["head_diameter"] = _L("Head diameter"); + m_desc["max_angle"] = _L("Max angle"); + m_desc["detection_radius"] = _L("Detection radius"); + m_desc["remove"] = _L("Remove"); + m_desc["remove_selected"] = _L("Selected"); + m_desc["remove_all"] = _L("All"); + m_desc["create"] = _L("Create"); + m_desc["auto_generate"] = _L("Auto-generate"); + m_desc["auto-generate-tooltip"] = _L("Generate brim ears using Max angle and Detection radius"); + m_desc["section_view"] = _L("Section view"); m_shortcuts = { - {_L("Left mouse button"), _L("Add a brim ear")}, - {_L("Right mouse button"), _L("Delete a brim ear")}, - {ctrl + _L("Mouse wheel"), _L("Adjust head diameter")}, - {alt + _L("Mouse wheel"), _L("Adjust section view")}, + {_L("Left mouse button"), _L("Add or Select")}, + {_L("Right mouse button"), _L("Remove")}, + {ctrl + _L("Mouse wheel"), m_desc["head_diameter"]}, + {alt + _L("Mouse wheel"), m_desc["section_view"]}, }; return true; @@ -545,6 +548,15 @@ void GLGizmoBrimEars::delete_selected_points() update_model_object(); } +bool GLGizmoBrimEars::has_selected_points() const +{ + for (const auto& entry : m_editing_cache) { + if (entry.selected) + return true; + } + return false; +} + void GLGizmoBrimEars::on_dragging(const UpdateData& data) { if (m_hover_id != -1) { @@ -634,21 +646,21 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi y = std::min(y, bottom_limit - win_h); GizmoImguiSetNextWIndowPos(x, y, ImGuiCond_Always, 0.0f, 0.0f); - const float currt_scale = m_parent.get_scale(); - ImGuiWrapper::push_toolbar_style(currt_scale); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0 * currt_scale, 5.0 * currt_scale)); - ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarSize, 4.0f * currt_scale); + const float f_scale = m_parent.get_scale(); + ImGuiWrapper::push_toolbar_style(f_scale); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); GizmoImguiBegin(get_name(), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); float space_size = m_imgui->get_style_scaling() * 8; - std::vector text_list = {m_desc["head_diameter"], m_desc["max_angle"], m_desc["detection_radius"], m_desc["clipping_of_view"]}; + std::vector text_list = {m_desc["head_diameter"], m_desc["max_angle"], m_desc["detection_radius"], m_desc["clipping_of_view"], + m_desc["create"], m_desc["remove"]}; float widest_text = m_imgui->find_widest_text(text_list); float caption_size = widest_text + space_size + ImGui::GetStyle().WindowPadding.x; float input_text_size = m_imgui->scaled(10.0f); float button_size = ImGui::GetFrameHeight(); - float list_width = input_text_size + ImGui::GetStyle().ScrollbarSize + 2 * currt_scale; + float list_width = input_text_size + ImGui::GetStyle().ScrollbarSize + 2 * f_scale; const float slider_icon_width = m_imgui->get_slider_icon_size().x; const float slider_width = list_width - space_size; @@ -662,12 +674,12 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi if (last_y != y) last_y = y; } - ImGui::AlignTextToFramePadding(); // Following is a nasty way to: // - save the initial value of the slider before one starts messing with it // - keep updating the head radius during sliding so it is continuosly refreshed in 3D scene // - take correct undo/redo snapshot after the user is done with moving the slider + ImGui::AlignTextToFramePadding(); float initial_value = m_new_point_head_diameter; m_imgui->text(m_desc["head_diameter"]); ImGui::SameLine(caption_size); @@ -684,8 +696,10 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi ImGui::SameLine(drag_left_width); ImGui::PushItemWidth(1.5 * slider_icon_width); ImGui::BBLDragFloat("##head_diameter_input", &m_new_point_head_diameter, 0.05f, 0.0f, 0.0f, "%.1f"); - ImGui::AlignTextToFramePadding(); + ImGui::Separator(); + + ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc["max_angle"]); ImGui::SameLine(caption_size); ImGui::PushItemWidth(slider_width); @@ -693,8 +707,8 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi ImGui::SameLine(drag_left_width); ImGui::PushItemWidth(1.5 * slider_icon_width); ImGui::BBLDragFloat("##max_angle_input", &m_max_angle, 0.05f, 0.0f, 180.0f, "%.1f"); - ImGui::AlignTextToFramePadding(); + ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc["detection_radius"]); ImGui::SameLine(caption_size); ImGui::PushItemWidth(slider_width); @@ -702,8 +716,37 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi ImGui::SameLine(drag_left_width); ImGui::PushItemWidth(1.5 * slider_icon_width); ImGui::BBLDragFloat("##detection_radius_input", &m_detection_radius, 0.05f, 0.0f, static_cast(m_detection_radius_max), "%.1f"); + ImGui::Separator(); + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc["create"]); + ImGui::SameLine(caption_size); + if (m_imgui->button(m_desc["auto_generate"], m_desc["auto-generate-tooltip"])) { + auto_generate(); + } + + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc["remove"]); + ImGui::SameLine(caption_size); + m_imgui->disabled_begin(has_selected_points() == false); + if (m_imgui->button(m_desc["remove_selected"])) { + delete_selected_points(); + } + m_imgui->disabled_end(); + ImGui::SameLine(); + m_imgui->disabled_begin(m_editing_cache.empty()); + if (m_imgui->button(m_desc["remove_all"])) { + if (m_editing_cache.size() > 0) { + select_point(AllPoints); + delete_selected_points(); + } + } + m_imgui->disabled_end(); + + ImGui::Separator(); + + ImGui::AlignTextToFramePadding(); float clp_dist = float(m_c->object_clipper()->get_position()); m_imgui->text(m_desc["section_view"]); ImGui::SameLine(caption_size); @@ -712,79 +755,72 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi ImGui::SameLine(drag_left_width); ImGui::PushItemWidth(1.5 * slider_icon_width); bool b_clp_dist_input = ImGui::BBLDragFloat("##section_view_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f"); - if (slider_clp_dist || b_clp_dist_input) { m_c->object_clipper()->set_position_by_ratio(clp_dist, false, true); } - ImGui::Separator(); - - // ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); - - float f_scale = m_parent.get_gizmos_manager().get_layout_scale(); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); - if (m_imgui->button(m_desc["auto_generate"])) { auto_generate(); } - - if (m_imgui->button(m_desc["remove_selected"])) { delete_selected_points(); } - float font_size = ImGui::GetFontSize(); - //ImGui::Dummy(ImVec2(font_size * 1, font_size * 1.3)); - ImGui::SameLine(); - if (m_imgui->button(m_desc["remove_all"])) { - if (m_editing_cache.size() > 0) { - select_point(AllPoints); - delete_selected_points(); - } + if (slider_clp_dist || b_clp_dist_input) { + m_c->object_clipper()->set_position_by_ratio(clp_dist, false, true); } - ImGui::PopStyleVar(1); - float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y; + ImGui::Separator(); GLGizmoUtils::render_tooltip_button(m_imgui, m_parent, m_shortcuts, x, y); - if (glb_cfg.opt_enum("brim_type") != btPainted) { - ImGui::SameLine(); - auto link_text = [&]() { - ImColor HyperColor = ImGuiWrapper::COL_ORCA; - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::to_ImVec4(ColorRGB::WARNING())); - float parent_width = ImGui::GetContentRegionAvail().x; + ImGui::SameLine(); + GLGizmoUtils::begin_right_aligned_buttons({_L("Done")}); + if (m_imgui->button(_L("Done"))) { + m_parent.reset_all_gizmos(); + } + + bool brim_not_painted = (obj_cfg.option("brim_type")) ? (obj_cfg.opt_enum("brim_type") != btPainted) : + (glb_cfg.opt_enum("brim_type") != btPainted); + bool has_invalid_ears = !m_single_brim.empty(); + + if (brim_not_painted || has_invalid_ears) { + ImGui::Separator(); + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_WARNING); + + float parent_width = ImGui::GetContentRegionAvail().x; + float font_size = ImGui::GetFontSize(); + + if (brim_not_painted) { m_imgui->text_wrapped(_L("Warning: The brim type is not set to \"painted\", the brim ears will not take effect!"), parent_width); - ImGui::PopStyleColor(); + + ImColor HyperColor = ImGuiWrapper::COL_ORCA; ImGui::PushStyleColor(ImGuiCol_Text, HyperColor.Value); - ImGui::Dummy(ImVec2(font_size * 1.8, font_size * 1.3)); + + ImGui::Dummy(ImVec2(font_size * 1.8f, 0.0f)); // Horizontal indent ImGui::SameLine(); m_imgui->bold_text(_u8L("Set the brim type of this object to \"painted\"")); - ImGui::PopStyleColor(); - // underline - ImVec2 lineEnd = ImGui::GetItemRectMax(); - lineEnd.y -= 2.0f; - ImVec2 lineStart = lineEnd; - lineStart.x = ImGui::GetItemRectMin().x; - ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, HyperColor); - if (ImGui::IsMouseHoveringRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), true)) { + + ImVec2 min = ImGui::GetItemRectMin(); + ImVec2 max = ImGui::GetItemRectMax(); + ImGui::GetWindowDrawList()->AddLine(ImVec2(min.x, max.y - 2.0f), ImVec2(max.x, max.y - 2.0f), HyperColor); + + if (ImGui::IsMouseHoveringRect(min, max)) { m_link_text_hover = true; + ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { DynamicPrintConfig new_conf = obj_cfg; new_conf.set_key_value("brim_type", new ConfigOptionEnum(btPainted)); mo->config.assign_config(new_conf); } - }else { + } else { m_link_text_hover = false; } - }; - - if (obj_cfg.option("brim_type")) { - if (obj_cfg.opt_enum("brim_type") != btPainted) { - link_text(); - } - }else { - link_text(); + ImGui::PopStyleColor(1); // Pop HyperColor } - } + if (has_invalid_ears) { + wxString out = _L("Warning") + ": " + std::to_string(m_single_brim.size()) + " " + _L("invalid brim ears"); + m_imgui->text_wrapped(out, parent_width); + } - if (!m_single_brim.empty()) { - wxString out = _L("Warning") + ": " + std::to_string(m_single_brim.size()) + _L(" invalid brim ears"); - m_imgui->warning_text(out); + ImGui::PopStyleColor(1); // Pop Warning Color + } else { + // Reset hover state if no warnings are active + m_link_text_hover = false; } GizmoImguiEnd(); - ImGui::PopStyleVar(2); + ImGui::PopStyleVar(1); // ImGuiStyleVar_FramePadding ImGuiWrapper::pop_toolbar_style(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.hpp index 8f50859130..deb6525bfa 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.hpp @@ -80,6 +80,7 @@ public: bool on_mouse(const wxMouseEvent& mouse_event) override; bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); void delete_selected_points(); + bool has_selected_points() const; void update_model_object(); //ClippingPlane get_sla_clipping_plane() const; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 5fed930321..ea8d3c374f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -2369,7 +2369,10 @@ void GLGizmoCut3D::apply_selected_connectors(std::function app void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors, float x, float y, float bottom_limit) { // Connectors section - + + float f_scale = m_parent.get_gizmos_manager().get_layout_scale(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); + ImGui::Separator(); // WIP : Auto : Need to implement @@ -2447,26 +2450,24 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors, flo ImGui::Separator(); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); render_tooltip_button(x, y); - float f_scale = m_parent.get_gizmos_manager().get_layout_scale(); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); - ImGui::SameLine(); + GLGizmoUtils::begin_right_aligned_buttons({_L("Confirm connectors"), _L("Cancel")}); + GLGizmoUtils::push_orca_button_style(); if (m_imgui->button(_L("Confirm connectors"))) { unselect_all_connectors(); set_connectors_editing(false); } + GLGizmoUtils::pop_orca_button_style(); - ImGui::SameLine(m_label_width + m_editing_window_width - m_imgui->calc_text_size(_L("Cancel")).x - m_imgui->get_style_scaling() * 8); - + ImGui::SameLine(); if (m_imgui->button(_L("Cancel"))) { reset_connectors(); set_connectors_editing(false); } - ImGui::PopStyleVar(2); + ImGui::PopStyleVar(1); // ImGuiStyleVar_FramePadding } void GLGizmoCut3D::render_build_size() @@ -2846,10 +2847,14 @@ void GLGizmoCut3D::render_snap_specific_input(const std::string& label, const wx void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors, float x, float y, float bottom_limit) { -// if (m_mode == size_t(CutMode::cutPlanar)) { + const bool has_connectors = !connectors.empty(); + const bool is_cut_plane_init = m_rotation_m.isApprox(Transform3d::Identity()) && m_bb_center.isApprox(m_plane_center); + + float f_scale = m_parent.get_gizmos_manager().get_layout_scale(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); + CutMode mode = CutMode(m_mode); if (mode == CutMode::cutPlanar || mode == CutMode::cutTongueAndGroove) { - const bool has_connectors = !connectors.empty(); m_imgui->disabled_begin(has_connectors); if (render_cut_mode_combo()) @@ -2864,7 +2869,6 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors, floa render_move_center_input(Z); ImGui::SameLine(); - const bool is_cut_plane_init = m_rotation_m.isApprox(Transform3d::Identity()) && m_bb_center.isApprox(m_plane_center); m_imgui->disabled_begin(is_cut_plane_init); std::string act_name = _u8L("Reset cutting plane"); if (render_reset_button("cut_plane", act_name)) { @@ -2873,26 +2877,13 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors, floa } m_imgui->disabled_end(); -// render_flip_plane_button(); - if (mode == CutMode::cutPlanar) { - add_vertical_scaled_interval(0.75f); + ImGui::Separator(); m_imgui->disabled_begin(!m_keep_upper || !m_keep_lower || m_keep_as_parts || (m_part_selection.valid() && m_part_selection.is_one_object())); if (m_imgui->button(has_connectors ? _L("Edit connectors") : _L("Add connectors"))) set_connectors_editing(true); m_imgui->disabled_end(); - - ImGui::SameLine(1.5f * m_control_width); - - m_imgui->disabled_begin(is_cut_plane_init && !has_connectors); - act_name = _u8L("Reset cut"); - if (m_imgui->button(wxString::FromUTF8(act_name), _L("Reset cutting plane and remove connectors"))) { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), act_name, UndoRedo::SnapshotType::GizmoAction); - reset_cut_plane(); - reset_connectors(); - } - m_imgui->disabled_end(); } else if (mode == CutMode::cutTongueAndGroove) { m_is_slider_editing_done = false; @@ -2978,21 +2969,35 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors, floa } ImGui::Separator(); + + render_tooltip_button(x, y); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); - float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y; - render_tooltip_button(x, get_cur_y); - - float f_scale = m_parent.get_gizmos_manager().get_layout_scale(); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); + if (mode == CutMode::cutPlanar) { + ImGui::SameLine(); + m_imgui->disabled_begin(is_cut_plane_init && !has_connectors); + if (m_imgui->button(_L("Reset"), _L("Reset cutting plane and remove connectors"))) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Reset Cut", UndoRedo::SnapshotType::GizmoAction); + reset_cut_plane(); + reset_connectors(); + } + m_imgui->disabled_end(); + } ImGui::SameLine(); + GLGizmoUtils::begin_right_aligned_buttons({_L("Perform cut"), _L("Cancel")}); m_imgui->disabled_begin(!can_perform_cut()); - if(m_imgui->button(_L("Perform cut"))) - perform_cut(m_parent.get_selection()); + GLGizmoUtils::push_orca_button_style(); + if(m_imgui->button(_L("Perform cut"))) + perform_cut(m_parent.get_selection()); + GLGizmoUtils::pop_orca_button_style(); m_imgui->disabled_end(); - ImGui::PopStyleVar(2); + ImGui::SameLine(); + if (m_imgui->button(_L("Cancel"))) { + m_parent.reset_all_gizmos(); + } + + ImGui::PopStyleVar(1); // ImGuiStyleVar_FramePadding } void GLGizmoCut3D::validate_connector_settings() @@ -3095,7 +3100,16 @@ void GLGizmoCut3D::init_input_window_data(CutConnectors &connectors) void GLGizmoCut3D::render_input_window_warning() const { - if (! m_invalid_connectors_idxs.empty()) { + const bool invalid_connector_warning = !m_invalid_connectors_idxs.empty(); + const bool keep_after_cut_warning = !m_keep_upper && !m_keep_lower; + const bool invalid_contour_warning = !has_valid_contour(); + const bool invalid_groove_warning = !has_valid_groove(); + + if (invalid_connector_warning || keep_after_cut_warning || invalid_contour_warning || invalid_groove_warning) { + ImGui::Separator(); + } + + if (invalid_connector_warning) { wxString out = /*wxString(ImGui::WarningMarkerSmall)*/ _L("Warning") + ": " + _L("Invalid connectors detected") + ":"; if (m_info_stats.outside_cut_contour > size_t(0)) out += "\n - " + format_wxstr(_L_PLURAL("%1$d connector is out of cut contour", "%1$d connectors are out of cut contour", m_info_stats.outside_cut_contour), @@ -3105,14 +3119,14 @@ void GLGizmoCut3D::render_input_window_warning() const m_info_stats.outside_bb); if (m_info_stats.is_overlap) out += "\n - " + _L("Some connectors are overlapped"); - m_imgui->text(out); + m_imgui->warning_text(out); } - if (!m_keep_upper && !m_keep_lower) - m_imgui->text(/*wxString(ImGui::WarningMarkerSmall)*/ _L("Warning") + ": " + _L("Select at least one object to keep after cutting.")); - if (!has_valid_contour()) - m_imgui->text(/*wxString(ImGui::WarningMarkerSmall)*/ _L("Warning") + ": " + _L("Cut plane is placed out of object")); - else if (!has_valid_groove()) - m_imgui->text(/*wxString(ImGui::WarningMarkerSmall)*/ _L("Warning") + ": " + _L("Cut plane with groove is invalid")); + if (keep_after_cut_warning) + m_imgui->warning_text(/*wxString(ImGui::WarningMarkerSmall)*/ _L("Warning") + ": " + _L("Select at least one object to keep after cutting.")); + if (invalid_contour_warning) + m_imgui->warning_text(/*wxString(ImGui::WarningMarkerSmall)*/ _L("Warning") + ": " + _L("Cut plane is placed out of object")); + else if (invalid_groove_warning) + m_imgui->warning_text(/*wxString(ImGui::WarningMarkerSmall)*/ _L("Warning") + ": " + _L("Cut plane with groove is invalid")); } void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 8766ad7b13..b7c963cc63 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -1267,6 +1267,59 @@ void GLGizmoEmboss::reset_volume() remove_notification_not_valid_font(); } +namespace { +// FIX IT: It should not change volume position before successfull change volume by process +void fix_transformation(const StyleManager::Style& from, const StyleManager::Style& to, GLCanvas3D& canvas) +{ + // fix Z rotation when exists difference in styles + const std::optional& f_angle_opt = from.angle; + const std::optional& t_angle_opt = to.angle; + if (!is_approx(f_angle_opt, t_angle_opt)) { + // fix rotation + float f_angle = f_angle_opt.value_or(.0f); + float t_angle = t_angle_opt.value_or(.0f); + do_local_z_rotate(canvas.get_selection(), t_angle - f_angle); + std::string no_snapshot; + canvas.do_rotate(no_snapshot); + } + + // fix distance (Z move) when exists difference in styles + const std::optional& f_move_opt = from.distance; + const std::optional& t_move_opt = to.distance; + if (!is_approx(f_move_opt, t_move_opt)) { + float f_move = f_move_opt.value_or(.0f); + float t_move = t_move_opt.value_or(.0f); + do_local_z_move(canvas.get_selection(), t_move - f_move); + std::string no_snapshot; + canvas.do_move(no_snapshot); + } +} +} // namespace + +bool GLGizmoEmboss::is_changed_from_default_style() +{ + int default_index = 0; + const StyleManager::Style& default_style = m_style_manager.get_styles()[default_index]; + const StyleManager::Style& current_style = m_style_manager.get_style(); + + return default_style == current_style; +} + +void GLGizmoEmboss::reset_to_default_style() +{ + int default_index = 0; + const StyleManager::Style& current_style = m_style_manager.get_style(); + const StyleManager::Style& default_style = m_style_manager.get_styles()[default_index]; + + StyleManager::Style cur_s = current_style; // copy + StyleManager::Style new_s = default_style; // copy + + if (m_style_manager.load_style(default_index)) { + ::fix_transformation(cur_s, new_s, m_parent); + process(); + } +} + void GLGizmoEmboss::calculate_scale() { Transform3d to_world = m_parent.get_selection().get_first_volume()->world_matrix(); auto to_world_linear = to_world.linear(); @@ -1406,9 +1459,29 @@ void GLGizmoEmboss::draw_window(float x, float y) draw_model_type(); } + ImGui::Spacing(); ImGui::Separator(); + ImGui::Spacing(); + float f_scale = m_parent.get_gizmos_manager().get_layout_scale(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); GLGizmoUtils::render_tooltip_button(m_imgui, m_parent, m_shortcuts, x, y); + + ImGui::SameLine(); + m_imgui->disabled_begin(is_changed_from_default_style()); + if (m_imgui->button(_L("Reset"), _L("Reset all options except the text and operation"))) { + reset_to_default_style(); + } + m_imgui->disabled_end(); + + ImGui::SameLine(); + GLGizmoUtils::begin_right_aligned_buttons({_L("Done")}); + + if (m_imgui->button(_L("Done"))) { + m_parent.reset_all_gizmos(); + } + + ImGui::PopStyleVar(1); // ImGuiStyleVar_FramePadding #ifdef SHOW_WX_FONT_DESCRIPTOR if (is_selected_style) @@ -2155,34 +2228,6 @@ void GLGizmoEmboss::draw_delete_style_button() { } } -namespace { -// FIX IT: It should not change volume position before successfull change volume by process -void fix_transformation(const StyleManager::Style &from, const StyleManager::Style &to, GLCanvas3D &canvas) { - // fix Z rotation when exists difference in styles - const std::optional &f_angle_opt = from.angle; - const std::optional &t_angle_opt = to.angle; - if (!is_approx(f_angle_opt, t_angle_opt)) { - // fix rotation - float f_angle = f_angle_opt.value_or(.0f); - float t_angle = t_angle_opt.value_or(.0f); - do_local_z_rotate(canvas.get_selection(), t_angle - f_angle); - std::string no_snapshot; - canvas.do_rotate(no_snapshot); - } - - // fix distance (Z move) when exists difference in styles - const std::optional &f_move_opt = from.distance; - const std::optional &t_move_opt = to.distance; - if (!is_approx(f_move_opt, t_move_opt)) { - float f_move = f_move_opt.value_or(.0f); - float t_move = t_move_opt.value_or(.0f); - do_local_z_move(canvas.get_selection(), t_move - f_move); - std::string no_snapshot; - canvas.do_move(no_snapshot); - } -} -} // namesapce - void GLGizmoEmboss::draw_style_list() { if (!m_style_manager.is_active_font()) return; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index f5916620f8..4ad41483ac 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -109,6 +109,9 @@ private: void set_volume_by_selection(); void reset_volume(); + bool is_changed_from_default_style(); + void reset_to_default_style(); + // create volume from text - main functionality bool process(bool make_snapshot = true); void close(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 7af55915d5..2f69ed64fa 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -82,7 +82,7 @@ bool GLGizmoFdmSupports::on_init() m_desc["perform"] = _L("Perform"); m_desc["on_overhangs_only"] = _L("On highlighted overhangs only"); - m_desc["remove_all"] = _L("Erase all painting"); + m_desc["remove_all"] = _L("Erase all"); m_desc["highlight_by_angle"] = _L("Highlight overhang areas"); m_desc["tool_type"] = _L("Tool type"); m_desc["gap_fill"] = _L("Gap fill"); @@ -215,6 +215,9 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l //BBS ImGuiWrapper::push_toolbar_style(m_parent.get_scale()); + float f_scale = m_parent.get_gizmos_manager().get_layout_scale(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); + GizmoImguiBegin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: @@ -225,11 +228,10 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l const float highlight_slider_left = m_imgui->calc_text_size(m_desc.at("highlight_by_angle")).x + m_imgui->scaled(1.5f); const float reset_button_slider_left = m_imgui->calc_text_size(m_desc.at("reset_direction")).x + m_imgui->scaled(1.5f) + ImGui::GetStyle().FramePadding.x * 2; const float on_overhangs_only_width = m_imgui->calc_text_size(m_desc["on_overhangs_only"]).x + m_imgui->scaled(1.5f); - const float remove_btn_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.5f); const float filter_btn_width = m_imgui->calc_text_size(m_desc.at("perform")).x + m_imgui->scaled(1.5f); const float gap_area_txt_width = m_imgui->calc_text_size(m_desc.at("gap_area")).x + m_imgui->scaled(1.5f); const float smart_fill_angle_txt_width = m_imgui->calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.5f); - const float buttons_width = remove_btn_width + filter_btn_width + m_imgui->scaled(1.5f); + const float buttons_width = filter_btn_width + m_imgui->scaled(1.5f); const float empty_button_width = m_imgui->calc_button_size("").x; const float tips_width = m_imgui->calc_text_size(_L("Auto support threshold angle: ") + " 90 ").x + m_imgui->scaled(1.5f); @@ -358,12 +360,27 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::SameLine(drag_left_width + sliders_left_width); ImGui::PushItemWidth(1.5 * slider_icon_width); ImGui::BBLDragFloat("##gap_area_input", &TriangleSelectorPatch::gap_area, 0.05f, 0.0f, 0.0f, "%.2f"); + + // Apply gap fill button + if (m_imgui->button(m_desc.at("perform"))) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Reset selection", UndoRedo::SnapshotType::GizmoAction); + + for (int i = 0; i < m_triangle_selectors.size(); i++) { + TriangleSelectorPatch* ts_mm = dynamic_cast(m_triangle_selectors[i].get()); + ts_mm->update_selector_triangles(); + ts_mm->request_update_render_data(true); + } + update_model_object(); + m_parent.set_as_dirty(); + } } - m_imgui->bbl_checkbox(m_desc["on_overhangs_only"], m_paint_on_overhangs_only); - if (ImGui::IsItemHovered()) - m_imgui->tooltip(format_wxstr(_L("Allows painting only on facets selected by: \"%1%\""), m_desc["highlight_by_angle"]), - max_tooltip_width); + if (m_current_tool != ImGui::GapFillIcon) { + m_imgui->bbl_checkbox(m_desc["on_overhangs_only"], m_paint_on_overhangs_only); + if (ImGui::IsItemHovered()) + m_imgui->tooltip(format_wxstr(_L("Allows painting only on facets selected by: \"%1%\""), m_desc["highlight_by_angle"]), + max_tooltip_width); + } ImGui::Separator(); @@ -427,33 +444,12 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l if (b_bbl_slider_float || b_drag_input) m_c->object_clipper()->set_position_by_ratio(clp_dist, true); - ImGui::Separator(); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); + render_tooltip_button(x, y); - float f_scale =m_parent.get_gizmos_manager().get_layout_scale(); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); - ImGui::SameLine(); - - // Perform button is for gap fill - if (m_current_tool == ImGui::GapFillIcon) { - if (m_imgui->button(m_desc.at("perform"))) { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Reset selection", UndoRedo::SnapshotType::GizmoAction); - - for (int i = 0; i < m_triangle_selectors.size(); i++) { - TriangleSelectorPatch* ts_mm = dynamic_cast(m_triangle_selectors[i].get()); - ts_mm->update_selector_triangles(); - ts_mm->request_update_render_data(true); - } - update_model_object(); - m_parent.set_as_dirty(); - } - } - - ImGui::SameLine(); - + m_imgui->disabled_begin(m_c->selection_info()->model_object()->is_fdm_support_painted() == false); if (m_imgui->button(m_desc.at("remove_all"))) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Reset selection", UndoRedo::SnapshotType::GizmoAction); ModelObject * mo = m_c->selection_info()->model_object(); @@ -468,11 +464,18 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l update_model_object(); m_parent.set_as_dirty(); } - ImGui::PopStyleVar(2); + m_imgui->disabled_end(); + + ImGui::SameLine(); + GLGizmoUtils::begin_right_aligned_buttons({_L("Done")}); + if (m_imgui->button(_L("Done"))) { + m_parent.reset_all_gizmos(); + } GizmoImguiEnd(); // BBS + ImGui::PopStyleVar(1); // ImGuiStyleVar_FramePadding ImGuiWrapper::pop_toolbar_style(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp index d80687544e..6689fcdcea 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp @@ -37,7 +37,7 @@ bool GLGizmoFuzzySkin::on_init() const wxString shift = GUI::shortkey_shift_prefix(); m_desc["reset_direction"] = _L("Reset direction"); - m_desc["remove_all"] = _L("Erase all painting"); + m_desc["remove_all"] = _L("Erase all"); m_desc["circle"] = _L("Circle"); m_desc["sphere"] = _L("Sphere"); m_desc["pointer"] = _L("Triangles"); @@ -137,6 +137,8 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim // BBS ImGuiWrapper::push_toolbar_style(m_parent.get_scale()); + float f_scale = m_parent.get_gizmos_manager().get_layout_scale(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); GizmoImguiBegin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); @@ -151,7 +153,6 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim const float cursor_type_radio_sphere = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f); const float cursor_type_radio_pointer = m_imgui->calc_text_size(m_desc["pointer"]).x + m_imgui->scaled(2.5f); - const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f); const float buttons_width = m_imgui->scaled(0.5f); const float minimal_slider_width = m_imgui->scaled(4.f); @@ -176,7 +177,6 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim const float empty_button_width = m_imgui->calc_button_size("").x; window_width = std::max(window_width, total_text_max); - window_width = std::max(window_width, button_width); window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer); window_width = std::max(window_width, tool_type_radio_left + tool_type_radio_brush + tool_type_radio_smart_fill); window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f)); @@ -299,14 +299,10 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim ImGui::Separator(); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); render_tooltip_button(x, y); - float f_scale = m_parent.get_gizmos_manager().get_layout_scale(); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); - ImGui::SameLine(); - + m_imgui->disabled_begin(mo->is_fuzzy_skin_painted() == false); if (m_imgui->button(m_desc.at("remove_all"))) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), _u8L("Reset selection"), UndoRedo::SnapshotType::GizmoAction); int idx = -1; @@ -320,6 +316,13 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim update_model_object(); m_parent.set_as_dirty(); } + m_imgui->disabled_end(); + + ImGui::SameLine(); + GLGizmoUtils::begin_right_aligned_buttons({_L("Done")}); + if (m_imgui->button(_L("Done"))) { + m_parent.reset_all_gizmos(); + } const DynamicPrintConfig &glb_cfg = wxGetApp().preset_bundle->prints.get_edited_preset().config; const bool has_object_fuzzy_override = obj_cfg.option("fuzzy_skin"); @@ -329,7 +332,7 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim float font_size = ImGui::GetFontSize(); auto link_text = [&]() { ImColor HyperColor = ImGuiWrapper::COL_ORCA; - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::to_ImVec4(ColorRGB::WARNING())); + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_WARNING); float parent_width = ImGui::GetContentRegionAvail().x; m_imgui->text_wrapped(_L("Warning: Fuzzy skin is disabled, painted fuzzy skin will not take effect!"), parent_width); ImGui::PopStyleColor(); @@ -350,10 +353,11 @@ void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_lim mo->config.assign_config(new_conf); } }; + ImGui::Separator(); link_text(); } - ImGui::PopStyleVar(2); + ImGui::PopStyleVar(1); // ImGuiStyleVar_FramePadding GizmoImguiEnd(); // BBS diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp index efc7b82fae..6c6f913276 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp @@ -1917,18 +1917,6 @@ void GLGizmoMeasure::show_selection_ui() if (m_show_reset_first_tip) { m_imgui->text(_L("Feature 1 has been reset, \nfeature 2 has been feature 1")); } - if (m_selected_wrong_feature_waring_tip) { - if (m_measure_mode == EMeasureMode::ONLY_ASSEMBLY) { - if (m_assembly_mode == AssemblyMode::FACE_FACE) { - m_imgui->warning_text(_L("Warning: please select Plane's feature.")); - } else if (m_assembly_mode == AssemblyMode::POINT_POINT) { - m_imgui->warning_text(_L("Warning: please select Point's or Circle's feature.")); - } - } - } - if (m_measure_mode == EMeasureMode::ONLY_ASSEMBLY && m_hit_different_volumes.size() == 1) { - m_imgui->warning_text(_L("Warning: please select two different meshes.")); - } } void GLGizmoMeasure::show_distance_xyz_ui() @@ -2175,10 +2163,21 @@ void GLGizmoMeasure::on_render_input_window(float x, float y, float bottom_limit ImGui::Separator(); show_distance_xyz_ui(); + ImGui::Separator(); + float f_scale = m_parent.get_gizmos_manager().get_layout_scale(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); GLGizmoUtils::render_tooltip_button(m_imgui, m_parent, m_shortcuts, x, y); + ImGui::SameLine(); + GLGizmoUtils::begin_right_aligned_buttons({ _L("Done") }); + if (m_imgui->button(_L("Done"))) { + m_parent.reset_all_gizmos(); + } + + ImGui::PopStyleVar(1); // ImGuiStyleVar_FramePadding + if (last_feature != m_curr_feature || last_mode != m_mode || last_selected_features != m_selected_features) { // the dialog may have changed its size, ask for an extra frame to render it properly last_feature = m_curr_feature; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index 72e880f85d..213e5db9a4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -103,7 +103,7 @@ bool GLGizmoMmuSegmentation::on_init() m_desc["edge_detection"] = _L("Edge detection"); m_desc["gap_area"] = _L("Gap area"); m_desc["perform"] = _L("Perform"); - m_desc["remove_all"] = _L("Erase all painting"); + m_desc["remove_all"] = _L("Erase all"); m_desc["circle"] = _L("Circle"); m_desc["sphere"] = _L("Sphere"); m_desc["pointer"] = _L("Triangles"); @@ -362,6 +362,9 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott // BBS ImGuiWrapper::push_toolbar_style(m_parent.get_scale()); + float f_scale = m_parent.get_gizmos_manager().get_layout_scale(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); + GizmoImguiBegin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: @@ -374,10 +377,9 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott const float gap_area_slider_left = m_imgui->calc_text_size(m_desc.at("gap_area")).x + m_imgui->scaled(1.5f) + space_size; const float height_range_slider_left = m_imgui->calc_text_size(m_desc.at("height_range")).x + m_imgui->scaled(2.f); - const float remove_btn_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f); const float filter_btn_width = m_imgui->calc_text_size(m_desc.at("perform")).x + m_imgui->scaled(1.f); const float remap_btn_width = m_imgui->calc_text_size(m_desc.at("perform_remap")).x + m_imgui->scaled(1.f); - const float buttons_width = remove_btn_width + filter_btn_width + remap_btn_width + m_imgui->scaled(2.f); + const float buttons_width = filter_btn_width + remap_btn_width + m_imgui->scaled(2.f); const float minimal_slider_width = m_imgui->scaled(4.f); const float color_button_width = m_imgui->calc_text_size(std::string_view{""}).x + m_imgui->scaled(1.75f); @@ -566,8 +568,6 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott m_cursor_type = TriangleSelector::CursorType::POINTER; m_tool_type = ToolType::BUCKET_FILL; - m_imgui->bbl_checkbox(m_desc["edge_detection"], m_detect_geometry_edge); - if (m_detect_geometry_edge) { ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc["smart_fill_angle"]); @@ -587,6 +587,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott // set to negative value to disable edge detection m_smart_fill_angle = -1.f; } + + m_imgui->bbl_checkbox(m_desc["edge_detection"], m_detect_geometry_edge); } else if (m_current_tool == ImGui::HeightRangeIcon) { m_tool_type = ToolType::BRUSH; @@ -613,6 +615,19 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott ImGui::SameLine(drag_left_width + sliders_left_width); ImGui::PushItemWidth(1.5 * slider_icon_width); ImGui::BBLDragFloat("##gap_area_input", &TriangleSelectorPatch::gap_area, 0.05f, 0.0f, 0.0f, "%.2f"); + + // Apply Gap fill button + if (m_imgui->button(m_desc.at("perform"))) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Gap fill", UndoRedo::SnapshotType::GizmoAction); + + for (int i = 0; i < m_triangle_selectors.size(); i++) { + TriangleSelectorPatch* ts_mm = dynamic_cast(m_triangle_selectors[i].get()); + ts_mm->update_selector_triangles(); + ts_mm->request_update_render_data(true); + } + update_model_object(); + m_parent.set_as_dirty(); + } } ImGui::Separator(); @@ -639,30 +654,10 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott ImGui::Separator(); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); render_tooltip_button(x, y); - float f_scale =m_parent.get_gizmos_manager().get_layout_scale(); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); - ImGui::SameLine(); - - if (m_current_tool == ImGui::GapFillIcon) { - if (m_imgui->button(m_desc.at("perform"))) { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Gap fill", UndoRedo::SnapshotType::GizmoAction); - - for (int i = 0; i < m_triangle_selectors.size(); i++) { - TriangleSelectorPatch* ts_mm = dynamic_cast(m_triangle_selectors[i].get()); - ts_mm->update_selector_triangles(); - ts_mm->request_update_render_data(true); - } - update_model_object(); - m_parent.set_as_dirty(); - } - - ImGui::SameLine(); - } - + m_imgui->disabled_begin(m_c->selection_info()->model_object()->is_mm_painted() == false); if (m_imgui->button(m_desc.at("remove_all"))) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Reset selection", UndoRedo::SnapshotType::GizmoAction); ModelObject * mo = m_c->selection_info()->model_object(); @@ -677,7 +672,15 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott update_model_object(); m_parent.set_as_dirty(); } - ImGui::PopStyleVar(2); + m_imgui->disabled_end(); + + ImGui::SameLine(); + GLGizmoUtils::begin_right_aligned_buttons({_L("Done")}); + if (m_imgui->button(_L("Done"))) { + m_parent.reset_all_gizmos(); + } + + ImGui::PopStyleVar(1); // ImGuiStyleVar_FramePadding GizmoImguiEnd(); // BBS diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp index 1051c5ab3c..b211a18fb2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp @@ -41,7 +41,7 @@ bool GLGizmoSeam::on_init() m_desc["enforce"] = _L("Enforce seam"); m_desc["block"] = _L("Block seam"); m_desc["remove"] = _L("Erase"); - m_desc["remove_all"] = _L("Erase all painting"); + m_desc["remove_all"] = _L("Erase all"); m_desc["circle"] = _L("Circle"); m_desc["sphere"] = _L("Sphere"); @@ -140,6 +140,8 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) wchar_t old_tool = m_current_tool; //BBS ImGuiWrapper::push_toolbar_style(m_parent.get_scale()); + float f_scale = m_parent.get_gizmos_manager().get_layout_scale(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); GizmoImguiBegin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); @@ -256,14 +258,10 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) ImGui::Separator(); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); GLGizmoUtils::render_tooltip_button(m_imgui, m_parent, m_shortcuts, x, y); - float f_scale =m_parent.get_gizmos_manager().get_layout_scale(); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); - ImGui::SameLine(); - + m_imgui->disabled_begin(m_c->selection_info()->model_object()->is_seam_painted() == false); if (m_imgui->button(m_desc.at("remove_all"))) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Reset selection", UndoRedo::SnapshotType::GizmoAction); ModelObject *mo = m_c->selection_info()->model_object(); @@ -278,7 +276,15 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) update_model_object(); m_parent.set_as_dirty(); } - ImGui::PopStyleVar(2); + m_imgui->disabled_end(); + + ImGui::SameLine(); + GLGizmoUtils::begin_right_aligned_buttons({_L("Done")}); + if (m_imgui->button(_L("Done"))) { + m_parent.reset_all_gizmos(); + } + + ImGui::PopStyleVar(1); // ImGuiStyleVar_FramePadding GizmoImguiEnd(); //BBS diff --git a/src/slic3r/GUI/Gizmos/GLGizmoUtils.cpp b/src/slic3r/GUI/Gizmos/GLGizmoUtils.cpp index 3566bccf12..00d608b80a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoUtils.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoUtils.cpp @@ -26,6 +26,7 @@ - [Confirm], [Cancel], [Done], ... are buttons that close the Tool Dialog - [Reset], [Button1], ... are buttons that do not! - Non-consequential buttons like [Cancel] and [Done] are always the right-most buttons + - [Confirm] buttons should use the orca_button_style to differentiate them from other buttons - Multiple warnings can show, but should only have one ImGui::Separator above - If no warnings is shown, dont render the ImGui::Separator @@ -33,43 +34,101 @@ namespace Slic3r::GUI::GLGizmoUtils { -void render_tooltip_button( - ImGuiWrapper* imgui_wrapper, const GLCanvas3D& canvas, const std::vector>& shortcuts, float x, float y) -{ - float caption_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y; - float caption_x_max = 0.f; - for (const auto& item : shortcuts) { - caption_x_max = std::max(caption_x_max, imgui_wrapper->calc_text_size(item.first).x); - } - caption_x_max += imgui_wrapper->calc_text_size(": "sv).x + 35.f; + void render_tooltip_button( + ImGuiWrapper* imgui_wrapper, const GLCanvas3D& canvas, const std::vector>& shortcuts, float x, float y) + { + float caption_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y; + float caption_x_max = 0.f; + for (const auto& item : shortcuts) { + caption_x_max = std::max(caption_x_max, imgui_wrapper->calc_text_size(item.first).x); + } + caption_x_max += imgui_wrapper->calc_text_size(": "sv).x + 35.f; - auto& gizmos_manager = canvas.get_gizmos_manager(); - ImTextureID normal_id = gizmos_manager.get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP); - ImTextureID hover_id = gizmos_manager.get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER); + auto& gizmos_manager = canvas.get_gizmos_manager(); + ImTextureID normal_id = gizmos_manager.get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP); + ImTextureID hover_id = gizmos_manager.get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER); - float scale = canvas.get_scale(); + float scale = canvas.get_scale(); #ifdef WIN32 - int dpi = get_dpi_for_window(wxGetApp().GetTopWindow()); - scale *= (float) dpi / (float) DPI_DEFAULT; + int dpi = get_dpi_for_window(wxGetApp().GetTopWindow()); + scale *= (float)dpi / (float)DPI_DEFAULT; #endif - ImVec2 button_size = ImVec2(25 * scale, 25 * scale); + ImVec2 button_size = ImVec2(25 * scale, 25 * scale); - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {0, 0}); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, { 0, 0 }); - ImGui::ImageButton3(normal_id, hover_id, button_size); + ImGui::ImageButton3(normal_id, hover_id, button_size); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip2(ImVec2(x, caption_y)); - for (const auto& item : shortcuts) { - imgui_wrapper->text_colored(ImGuiWrapper::COL_ACTIVE, item.first + ": "); - ImGui::SameLine(caption_x_max); - imgui_wrapper->text_colored(ImGuiWrapper::COL_WINDOW_BG, item.second); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip2(ImVec2(x, caption_y)); + for (const auto& item : shortcuts) { + imgui_wrapper->text_colored(ImGuiWrapper::COL_ACTIVE, item.first + ": "); + ImGui::SameLine(caption_x_max); + imgui_wrapper->text_colored(ImGuiWrapper::COL_WINDOW_BG, item.second); + } + ImGui::EndTooltip(); } - ImGui::EndTooltip(); + ImGui::PopStyleVar(2); + } + + void begin_right_aligned_buttons(const std::vector& labels) + { + float total_width = 0.0f; + ImGuiStyle& style = ImGui::GetStyle(); + float spacing = style.ItemSpacing.x; + float padding = style.FramePadding.x * 2.0f; + + // Calculate width + for (size_t i = 0; i < labels.size(); ++i) { + total_width += ImGuiWrapper::calc_text_size(labels[i]).x + padding; + if (i < labels.size() - 1) + total_width += spacing; + } + + float avail = ImGui::GetContentRegionAvail().x; + + // Handle Overlap: If the total width of the buttons exceeds available space, move to a new line + if (total_width > avail) { + ImGui::NewLine(); + avail = ImGui::GetContentRegionAvail().x; // Reset to full window width + } + + float posX = ImGui::GetCursorPosX() + std::max(0.0f, avail - total_width); + ImGui::SetCursorPosX(posX); + } + + void push_orca_button_style() + { + ImVec4 base_orca = ImGuiWrapper::COL_ORCA; + + float h, s, v; + ImGui::ColorConvertRGBtoHSV(base_orca.x, base_orca.y, base_orca.z, h, s, v); + + ImVec4 hover, active; + + // Lighter variant for Hover (Increase Value by ~12%) + ImGui::ColorConvertHSVtoRGB(h, s, std::min(v + 0.12f, 1.0f), hover.x, hover.y, hover.z); + hover.w = base_orca.w; + + // Darker variant for Active (Decrease Value by ~12%) + ImGui::ColorConvertHSVtoRGB(h, s, std::max(v - 0.12f, 0.0f), active.x, active.y, active.z); + active.w = base_orca.w; + + ImGui::PushStyleColor(ImGuiCol_Button, base_orca); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, hover); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, active); + + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_WINDOW_BG); + + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); + } + + void pop_orca_button_style() + { + ImGui::PopStyleVar(1); + ImGui::PopStyleColor(4); } - ImGui::PopStyleVar(2); -} } // namespace Slic3r::GUI::GLGizmoUtils \ No newline at end of file diff --git a/src/slic3r/GUI/Gizmos/GLGizmoUtils.hpp b/src/slic3r/GUI/Gizmos/GLGizmoUtils.hpp index acdff4ddfe..1eed68b2cd 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoUtils.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoUtils.hpp @@ -15,9 +15,16 @@ class GLCanvas3D; namespace GLGizmoUtils { -// Renders a tooltip button using the provided shortcuts -void render_tooltip_button( - ImGuiWrapper* imgui_wrapper, const GLCanvas3D& canvas, const std::vector>& shortcuts, float x, float y); + // Renders a tooltip button using the provided shortcuts + void render_tooltip_button( + ImGuiWrapper* imgui_wrapper, const GLCanvas3D& canvas, const std::vector>& shortcuts, float x, float y); + + // Sets up ImGui to render buttons that are right-aligned within the current window, using the provided labels to calculate spacing. + void begin_right_aligned_buttons(const std::vector& labels); + + void push_orca_button_style(); + + void pop_orca_button_style(); } // namespace GLGizmoUtils } // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.cpp b/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.cpp index c40bc7d0ad..5eb4f44280 100644 --- a/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.cpp +++ b/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.cpp @@ -62,16 +62,21 @@ GizmoObjectManipulation::GizmoObjectManipulation(GLCanvas3D& glcanvas) m_shortcuts_move = { {alt + _L("Left mouse button"), _L("Part selection")}, - {shift + _L("Left mouse button"), _L("Fixed step drag")} + {shift + _L("Left mouse button"), _L("Fixed step drag")}, + {_L("Context Menu"), _L("Toggle Auto-Drop")} }; m_shortcuts_rotate = { - {alt + _L("Left mouse button"), _L("Part selection")}}; + {alt + _L("Left mouse button"), _L("Part selection")}, + {_L("Context Menu"), _L("Toggle Auto-Drop")} + }; m_shortcuts_scale = { {alt + _L("Left mouse button"), _L("Part selection")}, {shift + _L("Left mouse button"), _L("Fixed step drag")}, - {ctrl + _L("Left mouse button"), _L("Single sided scaling")}}; + {ctrl + _L("Left mouse button"), _L("Single sided scaling")}, + {_L("Context Menu"), _L("Toggle Auto-Drop")} + }; } void GizmoObjectManipulation::UpdateAndShow(const bool show) @@ -860,13 +865,25 @@ void GizmoObjectManipulation::do_render_move_window(ImGuiWrapper *imgui_wrapper, } } if (!focued_on_text) m_glcanvas.handle_sidebar_focus_event("", false); - + + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + float f_scale = m_glcanvas.get_gizmos_manager().get_layout_scale(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); + GLGizmoUtils::render_tooltip_button(imgui_wrapper, m_glcanvas, m_shortcuts_move, x, y); + ImGui::SameLine(); + GLGizmoUtils::begin_right_aligned_buttons({ _L("Done") }); + if (imgui_wrapper->button(_L("Done"))) { + m_glcanvas.reset_all_gizmos(); + } + m_last_active_item = current_active_id; last_move_input_window_width = ImGui::GetWindowWidth(); imgui_wrapper->end(); - ImGui::PopStyleVar(1); + ImGui::PopStyleVar(2); ImGuiWrapper::pop_toolbar_style(); } @@ -1052,14 +1069,26 @@ void GizmoObjectManipulation::do_render_rotate_window(ImGuiWrapper *imgui_wrappe if (!focued_on_text && !absolute_focued_on_text) m_glcanvas.handle_sidebar_focus_event("", false); + ImGui::Spacing(); // needed after Text + ImGui::Separator(); + ImGui::Spacing(); + float f_scale = m_glcanvas.get_gizmos_manager().get_layout_scale(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); + GLGizmoUtils::render_tooltip_button(imgui_wrapper, m_glcanvas, m_shortcuts_rotate, x, y); + + ImGui::SameLine(); + GLGizmoUtils::begin_right_aligned_buttons({ _L("Done") }); + if (imgui_wrapper->button(_L("Done"))) { + m_glcanvas.reset_all_gizmos(); + } m_last_active_item = current_active_id; last_rotate_input_window_width = ImGui::GetWindowWidth(); imgui_wrapper->end(); // BBS - ImGui::PopStyleVar(1); + ImGui::PopStyleVar(2); ImGuiWrapper::pop_toolbar_style(); } @@ -1214,7 +1243,7 @@ void GizmoObjectManipulation::do_render_scale_input_window(ImGuiWrapper* imgui_w if (display_size.x() > 0 && display_size.y() > 0 && display_size.z() > 0) { m_buffered_size = display_size; } - ImGui::Separator(); + ImGui::AlignTextToFramePadding(); bool is_avoid_one_update{false}; if (combox_changed) { @@ -1286,15 +1315,26 @@ void GizmoObjectManipulation::do_render_scale_input_window(ImGuiWrapper* imgui_w } if (!focued_on_text) m_glcanvas.handle_sidebar_focus_event("", false); + + ImGui::Separator(); + float f_scale = m_glcanvas.get_gizmos_manager().get_layout_scale(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); GLGizmoUtils::render_tooltip_button(imgui_wrapper, m_glcanvas, m_shortcuts_scale, x, y); + ImGui::SameLine(); + GLGizmoUtils::begin_right_aligned_buttons({ _L("Done") }); + if (imgui_wrapper->button(_L("Done"))) { + m_glcanvas.reset_all_gizmos(); + } + m_last_active_item = current_active_id; last_scale_input_window_width = ImGui::GetWindowWidth(); imgui_wrapper->end(); //BBS + ImGui::PopStyleVar(1); ImGuiWrapper::pop_toolbar_style(); } diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 85f59d31e5..7743ff8e00 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -185,6 +185,7 @@ const ImVec4 ImGuiWrapper::COL_TOOLBAR_BG = { 250 / 255.f, 250 / 255.f, 2 const ImVec4 ImGuiWrapper::COL_TOOLBAR_BG_DARK = { 57 / 255.f, 60 / 255.f, 66 / 255.f, 1.f }; // ORCA color matches with toolbar_background_dark.png const ImVec4 ImGuiWrapper::COL_ORCA = to_ImVec4(ColorRGBA::ORCA()); const ImVec4 ImGuiWrapper::COL_MODIFIED = { 253.f / 255.f, 111.f / 255.f, 40.f / 255.f, 1}; // ORCA same color with m_color_label_modified +const ImVec4 ImGuiWrapper::COL_WARNING = to_ImVec4(ColorRGB::WARNING()); int ImGuiWrapper::TOOLBAR_WINDOW_FLAGS = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove @@ -871,8 +872,8 @@ bool ImGuiWrapper::button(const wxString &label, const wxString& tooltip) const bool ret = ImGui::Button(label_utf8.c_str()); if (!tooltip.IsEmpty() && ImGui::IsItemHovered()) { - auto tooltip_utf8 = into_u8(tooltip); - ImGui::SetTooltip(tooltip_utf8.c_str(), nullptr); + const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; + this->tooltip(tooltip, max_tooltip_width); } return ret; @@ -884,8 +885,8 @@ bool ImGuiWrapper::bbl_button(const wxString &label, const wxString& tooltip) const bool ret = ImGui::BBLButton(label_utf8.c_str()); if (!tooltip.IsEmpty() && ImGui::IsItemHovered()) { - auto tooltip_utf8 = into_u8(tooltip); - ImGui::SetTooltip(tooltip_utf8.c_str(), nullptr); + const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; + this->tooltip(tooltip, max_tooltip_width); } return ret; @@ -1055,7 +1056,7 @@ void ImGuiWrapper::text(const wxString &label) void ImGuiWrapper::warning_text(const char *label) { - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::to_ImVec4(ColorRGB::WARNING())); + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_WARNING); this->text(label); ImGui::PopStyleColor(); } diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index a2b8476c71..8855a38405 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -349,6 +349,7 @@ public: static const ImVec4 COL_SEPARATOR_DARK; static const ImVec4 COL_ORCA; static const ImVec4 COL_MODIFIED; + static const ImVec4 COL_WARNING; //BBS static void on_change_color_mode(bool is_dark); From f0392ab2263d1398c64ffa367185c66e5200d7f6 Mon Sep 17 00:00:00 2001 From: yw4z Date: Mon, 18 May 2026 08:59:22 +0300 Subject: [PATCH 045/117] Show filament edit button or action menu dynamically depends on single / multi materilal usage (#12651) * init * fix build error * add guards * Update Plater.cpp --- src/slic3r/GUI/GUI_Factories.cpp | 25 +++++----- src/slic3r/GUI/Plater.cpp | 79 ++++++++++++++++++-------------- src/slic3r/GUI/Plater.hpp | 2 +- src/slic3r/GUI/Tab.cpp | 6 +-- 4 files changed, 61 insertions(+), 51 deletions(-) diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 8aa227ebdc..2a47fe1aad 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -1644,17 +1644,6 @@ void MenuFactory::create_filament_action_menu(bool init, int active_filament_men []() { return true; }, m_parent); } - if (init) { - append_menu_item( - menu, wxID_ANY, _L("Delete"), _L("Delete this filament"), [](wxCommandEvent&) { - plater()->sidebar().delete_filament(-2); }, "", nullptr, - []() { - return plater()->sidebar().combos_filament().size() > 1 - // Orca: only show delete filament option for SEMM machines unless is BBL - && Sidebar::should_show_SEMM_buttons(); - }, m_parent); - } - const int item_id = menu->FindItem(_L("Merge with")); if (item_id != wxNOT_FOUND) menu->Destroy(item_id); @@ -1675,6 +1664,20 @@ void MenuFactory::create_filament_action_menu(bool init, int active_filament_men } append_submenu(menu, sub_menu, wxID_ANY, _L("Merge with"), "", "", [filaments_cnt]() { return filaments_cnt > 1; }, m_parent); + + // ORCA use delete item on end of menu to prevent accidental clicks. clicking to submenus(merge) already not allowed by OS + const int delete_id = menu->FindItem(_L("Delete")); + if (delete_id != wxNOT_FOUND) + menu->Destroy(delete_id); + + append_menu_item( + menu, wxID_ANY, _L("Delete"), _L("Delete this filament"), [](wxCommandEvent&) { + plater()->sidebar().delete_filament(-2); }, "", nullptr, + []() { + return plater()->sidebar().combos_filament().size() > 1 + // Orca: only show delete filament option for SEMM machines unless is BBL + && Sidebar::should_show_SEMM_buttons(); + }, m_parent); } //BBS: add part plate related logic diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 48d8c6c8c1..c3f62f90d4 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2403,13 +2403,23 @@ void Sidebar::init_filament_combo(PlaterPresetComboBox **combo, const int filame edit_btn->SetToolTip(_L("Click to edit preset")); PlaterPresetComboBox* combobox = (*combo); - edit_btn->Bind(wxEVT_BUTTON, [this, edit_btn, filament_idx](wxCommandEvent) { - auto menu = p->plater->filament_action_menu(filament_idx); - wxPoint pt { 0, edit_btn->GetSize().GetHeight() + 10 }; - pt = edit_btn->ClientToScreen(pt); - pt = wxGetApp().mainframe->ScreenToClient(pt); - p->m_menu_filament_id = filament_idx; - p->plater->PopupMenu(menu, (int) pt.x, pt.y); + edit_btn->Bind(wxEVT_BUTTON, [this, edit_btn, combobox, filament_idx](wxCommandEvent) { + bool single_or_bbl = should_show_SEMM_buttons(); + bool is_multi_material = p->combos_filament.size() > 1; + if(single_or_bbl && is_multi_material) { + // MULTI MATERIAL Show menu + auto menu = p->plater->filament_action_menu(filament_idx); + wxPoint pt { 0, edit_btn->GetSize().GetHeight() + FromDIP(2) }; + pt = edit_btn->ClientToScreen(pt); + pt = wxGetApp().mainframe->ScreenToClient(pt); + p->m_menu_filament_id = filament_idx; + p->plater->PopupMenu(menu, (int) pt.x, pt.y); + } + else { + // SINGLE MATERIAL / MULTI EXTRUDER / TOOLCHANGER / IDEX Opens Dialog directly + p->editing_filament = filament_idx; + combobox->switch_to_tab(); + } }); combobox->edit_btn = edit_btn; @@ -2510,7 +2520,7 @@ void Sidebar::update_all_preset_comboboxes() p->m_filament_icon->SetBitmap_("filament"); } - show_SEMM_buttons(should_show_SEMM_buttons()); + show_SEMM_buttons(); //p->m_staticText_filament_settings->Update(); @@ -3112,16 +3122,7 @@ void Sidebar::on_filament_count_change(size_t num_filaments) // remove unused choices if any remove_unused_filament_combos(num_filaments); - auto sizer = p->m_panel_filament_title->GetSizer(); - if (p->m_flushing_volume_btn != nullptr && sizer != nullptr) { - if (num_filaments > 1) { - sizer->Show(p->m_flushing_volume_btn); - sizer->Show(p->m_bpButton_del_filament); // ORCA: Show delete filament button if multiple filaments - } else { - sizer->Hide(p->m_flushing_volume_btn); - sizer->Hide(p->m_bpButton_del_filament); // ORCA: Hide delete filament button if there is only one filament - } - } + show_SEMM_buttons(); // ORCA update_filaments_area_height(); // ORCA @@ -3168,16 +3169,7 @@ void Sidebar::on_filaments_delete(size_t filament_id) } } - auto sizer = p->m_panel_filament_title->GetSizer(); - if (p->m_flushing_volume_btn != nullptr && sizer != nullptr) { - if (p->combos_filament.size() > 1) { - sizer->Show(p->m_flushing_volume_btn); - sizer->Show(p->m_bpButton_del_filament); // ORCA: Show delete filament button if multiple filaments - } else { - sizer->Hide(p->m_flushing_volume_btn); - sizer->Hide(p->m_bpButton_del_filament); // ORCA: Hide delete filament button if there is only one filament - } - } + show_SEMM_buttons(); // ORCA for (size_t idx = filament_id ; idx < p->combos_filament.size(); ++idx) { p->combos_filament[idx]->update(); @@ -3736,14 +3728,31 @@ bool Sidebar::should_show_SEMM_buttons() return cfg.opt_bool("single_extruder_multi_material") || is_bbl_vendor; } -void Sidebar::show_SEMM_buttons(bool bshow) +void Sidebar::show_SEMM_buttons() { - if(p->m_bpButton_add_filament) - p->m_bpButton_add_filament->Show(bshow); - if (p->m_bpButton_del_filament && p->combos_filament.size() > 1) // ORCA add filament count as condition to prevent showing Flushing volumes and Del Filament icon visible while only 1 filament exist - p->m_bpButton_del_filament->Show(bshow); - if (p->m_flushing_volume_btn && p->combos_filament.size() > 1) // ORCA add filament count as condition to prevent showing Flushing volumes and Del Filament icon visible while only 1 filament exist - p->m_flushing_volume_btn->Show(bshow); + // ORCA + if (!p || p->combos_filament.empty() || !p->m_bpButton_add_filament || !p->m_bpButton_del_filament || !p->m_flushing_volume_btn) + return; + + bool is_multi_material = p->combos_filament.size() > 1; + bool single_or_bbl = should_show_SEMM_buttons(); + bool is_single = single_or_bbl && !is_multi_material; // SINGLE EXTRUDER / BBL WITH 1 MATERIAL + bool is_multi = single_or_bbl && is_multi_material; // MULTI MATERIAL WITH SINGLE EXTRUDER + bool is_fixed = !is_single && !is_multi; // MULTI EXTRUDER / TOOLCHANGER / IDEX WITH FIXED MATERIAL + + p->m_bpButton_add_filament->Show(single_or_bbl); + p->m_bpButton_del_filament->Show(is_multi); + p->m_flushing_volume_btn->Show( is_multi); + + if (is_multi) { + for (auto &c : p->combos_filament) + c->edit_btn->SetBitmap_("menu_filament"); + } + else if (is_single || is_fixed) { + for (auto &c : p->combos_filament) + c->edit_btn->SetBitmap_("edit"); + } + Layout(); } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 31e0e2f0e1..f2089c1c4f 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -203,7 +203,7 @@ public: void get_small_btn_sync_pos_size(wxPoint &pt, wxSize &size); // Orca static bool should_show_SEMM_buttons(); - void show_SEMM_buttons(bool bshow); + void show_SEMM_buttons(); void update_dynamic_filament_list(); PlaterPresetComboBox * printer_combox(); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 9e58d7f9f5..ccf595b22a 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1554,8 +1554,7 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) if (opt_key == "single_extruder_multi_material" ){ - const auto bSEMM = m_config->opt_bool("single_extruder_multi_material"); - wxGetApp().sidebar().show_SEMM_buttons(bSEMM); + wxGetApp().sidebar().show_SEMM_buttons(); wxGetApp().get_tab(Preset::TYPE_PRINT)->update(); } @@ -1606,8 +1605,7 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) if (opt_key == "single_extruder_multi_material" ){ - const auto bSEMM = m_config->opt_bool("single_extruder_multi_material"); - wxGetApp().sidebar().show_SEMM_buttons(bSEMM); + wxGetApp().sidebar().show_SEMM_buttons(); wxGetApp().get_tab(Preset::TYPE_PRINT)->update(); } From 232fc30db1bcde98120d11f72d02236f051825c1 Mon Sep 17 00:00:00 2001 From: Ian Chua Date: Mon, 18 May 2026 15:18:31 +0800 Subject: [PATCH 046/117] log: add logs surrounding logout to potentially catch any unwanted logouts or errors --- src/slic3r/GUI/GUI_App.cpp | 21 +++++++++++++++------ src/slic3r/GUI/WebGuideDialog.cpp | 5 ++++- src/slic3r/Utils/OrcaCloudServiceAgent.cpp | 1 + 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 3169e53eee..889cccde0f 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -1900,6 +1900,7 @@ void GUI_App::init_networking_callbacks() } if (return_code == 5) { GUI::wxGetApp().CallAfter([this, provider = event.provider] { + BOOST_LOG_TRIVIAL(info) << "logout: login expired"; this->request_user_logout(provider); MessageDialog msg_dlg(nullptr, _L("Login information expired. Please login again."), "", wxAPPLY | wxOK); if (msg_dlg.ShowModal() == wxOK) { @@ -4526,7 +4527,8 @@ std::string GUI_App::handle_web_request(std::string cmd) } else if (command_str.compare("homepage_logout") == 0) { CallAfter([this] { - wxGetApp().request_user_logout(); + BOOST_LOG_TRIVIAL(info) << "logout: homepage_logout"; + request_user_logout(); }); } else if (command_str.compare("get_orca_login_info") == 0) { @@ -4539,18 +4541,22 @@ std::string GUI_App::handle_web_request(std::string cmd) CallAfter([this] { request_login(true, BBL_CLOUD_PROVIDER); }); } else if (command_str.compare("homepage_bambu_logout") == 0) { - CallAfter([this] { request_user_logout(BBL_CLOUD_PROVIDER); }); + CallAfter([this] { + BOOST_LOG_TRIVIAL(info) << "logout: homepage_bambu_logout"; + request_user_logout(BBL_CLOUD_PROVIDER); + }); } else if (command_str.compare("homepage_orca_login_or_register") == 0) { CallAfter([this] { request_login(true, ORCA_CLOUD_PROVIDER); }); } else if (command_str.compare("homepage_orca_logout") == 0) { - CallAfter([this] { request_user_logout(ORCA_CLOUD_PROVIDER); }); + CallAfter([this] { + BOOST_LOG_TRIVIAL(info) << "logout: homepage_orca_logout"; + request_user_logout(ORCA_CLOUD_PROVIDER); + }); } else if (command_str.compare("homepage_modeldepot") == 0) { - CallAfter([this] { - wxGetApp().open_mall_page_dialog(); - }); + CallAfter([this] { open_mall_page_dialog(); }); } else if (command_str.compare("homepage_newproject") == 0) { this->request_open_project(""); @@ -4826,6 +4832,7 @@ void GUI_App::on_http_error(wxCommandEvent &evt) if (status == 401) { if (m_agent) { if (m_agent->is_user_login(provider)) { + BOOST_LOG_TRIVIAL(warning) << "logout: http error 401."; this->request_user_logout(provider); if (!m_show_http_errpr_msgdlg) { @@ -5599,6 +5606,7 @@ void GUI_App::show_check_privacy_dlg(wxCommandEvent& evt) privacy_dlg.Bind(EVT_PRIVACY_UPDATE_CANCEL, [this, provider](wxCommandEvent &e) { app_config->set_bool("privacy_update_checked", false); if (m_agent) { + BOOST_LOG_TRIVIAL(info) << "logout: Privacy update dialog cancelled."; m_agent->user_logout(false, provider); post_logout_to_webview(provider); } @@ -6766,6 +6774,7 @@ void GUI_App::stop_sync_user_preset() void GUI_App::on_stealth_mode_enter() { stop_sync_user_preset(); + BOOST_LOG_TRIVIAL(info) << "logout: on_stealth_mode_enter"; request_user_logout(ORCA_CLOUD_PROVIDER); request_user_logout(BBL_CLOUD_PROVIDER); if (mainframe && mainframe->m_webview) { diff --git a/src/slic3r/GUI/WebGuideDialog.cpp b/src/slic3r/GUI/WebGuideDialog.cpp index 575748fce0..f68c00ff40 100644 --- a/src/slic3r/GUI/WebGuideDialog.cpp +++ b/src/slic3r/GUI/WebGuideDialog.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "I18N.hpp" #include "libslic3r/AppConfig.hpp" @@ -516,7 +517,9 @@ void GuideFrame::OnScriptMessage(wxWebViewEvent &evt) if (agent) { agent->set_country_code(country_code); if (wxGetApp().is_user_login()) { - agent->user_logout(); + BOOST_LOG_TRIVIAL(info) << "logout: user_logout on user_guide_finish"; + // agent->user_logout(); + wxGetApp().request_user_logout(); } } } diff --git a/src/slic3r/Utils/OrcaCloudServiceAgent.cpp b/src/slic3r/Utils/OrcaCloudServiceAgent.cpp index 4298f9616c..2b32b428ea 100644 --- a/src/slic3r/Utils/OrcaCloudServiceAgent.cpp +++ b/src/slic3r/Utils/OrcaCloudServiceAgent.cpp @@ -1465,6 +1465,7 @@ bool OrcaCloudServiceAgent::load_refresh_token(std::string& out_token) if (payload.rfind("v2:", 0) == 0) { auto delim = payload.find(':', 3); if (delim == std::string::npos) { + BOOST_LOG_TRIVIAL(warning) << "payload missing delim ':'."; integrity_ok = false; } else { std::string stored_hmac = payload.substr(3, delim - 3); From 6a06b63d6fcabd90445ca5e6cfb668aa7af9a34f Mon Sep 17 00:00:00 2001 From: Ian Chua Date: Mon, 18 May 2026 16:12:03 +0800 Subject: [PATCH 047/117] add logs to http_get/post/put/delete --- src/slic3r/Utils/OrcaCloudServiceAgent.cpp | 27 +++++++++++++--------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/slic3r/Utils/OrcaCloudServiceAgent.cpp b/src/slic3r/Utils/OrcaCloudServiceAgent.cpp index 2b32b428ea..0e1027cd46 100644 --- a/src/slic3r/Utils/OrcaCloudServiceAgent.cpp +++ b/src/slic3r/Utils/OrcaCloudServiceAgent.cpp @@ -1784,7 +1784,8 @@ int OrcaCloudServiceAgent::http_get(const std::string& path, std::string* respon std::string url = api_base_url + path; BOOST_LOG_TRIVIAL(trace) << "OrcaCloudServiceAgent: GET " << url; - ensure_token_fresh("http_get_" + path); + if (!ensure_token_fresh("http_get_" + path)) + BOOST_LOG_TRIVIAL(warning) << "ensure_token_fresh returned false"; struct HttpResult { bool success{false}; @@ -1816,8 +1817,9 @@ int OrcaCloudServiceAgent::http_get(const std::string& path, std::string* respon }) .on_error([&](std::string body, std::string error, unsigned resp_status) { result.success = false; - result.status = resp_status; - result.body = body; + result.status = resp_status == 0 ? 404 : resp_status; + result.body = body; + BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: HTTP error - " << error; }) .timeout_max(30) .perform_sync(); @@ -1885,8 +1887,9 @@ int OrcaCloudServiceAgent::http_post(const std::string& path, const std::string& }) .on_error([&](std::string resp_body, std::string error, unsigned resp_status) { result.success = false; - result.status = resp_status; - result.body = resp_body; + result.status = resp_status == 0 ? 404 : resp_status; + result.body = body; + BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: HTTP error - " << error; }) .timeout_max(30) .perform_sync(); @@ -1954,8 +1957,9 @@ int OrcaCloudServiceAgent::http_put(const std::string& path, const std::string& }) .on_error([&](std::string resp_body, std::string error, unsigned resp_status) { result.success = false; - result.status = resp_status; - result.body = resp_body; + result.status = resp_status == 0 ? 404 : resp_status; + result.body = body; + BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: HTTP error - " << error; }) .timeout_max(30) .perform_sync(); @@ -2020,8 +2024,9 @@ int OrcaCloudServiceAgent::http_delete(const std::string& path, std::string* res }) .on_error([&](std::string resp_body, std::string error, unsigned resp_status) { result.success = false; - result.status = resp_status; - result.body = resp_body; + result.status = resp_status == 0 ? 404 : resp_status; + result.body = resp_body; + BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: HTTP error - " << error; }) .timeout_max(30) .perform_sync(); @@ -2102,7 +2107,7 @@ bool OrcaCloudServiceAgent::http_post_token(const std::string& body, std::string }) .on_error([&](std::string body, std::string error, unsigned resp_status) { success = false; - status = resp_status; + status = resp_status == 0 ? 404 : resp_status; resp_body = body; BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: HTTP error - " << error; }) @@ -2172,7 +2177,7 @@ bool OrcaCloudServiceAgent::http_post_auth(const std::string& path, const std::s }) .on_error([&](std::string body, std::string error, unsigned resp_status) { success = false; - status = resp_status; + status = resp_status == 0 ? 404 : resp_status; resp_body = body; BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: HTTP (auth) error - " << error; }) From e5ca01ba9e9c380189a5677fa213b5bc217e3af2 Mon Sep 17 00:00:00 2001 From: Ioannis Giannakas <59056762+igiannakas@users.noreply.github.com> Date: Mon, 18 May 2026 09:24:28 +0100 Subject: [PATCH 048/117] Fix per-volume "Only one wall" overrides being ignored on assembly parts (#13714) --- src/libslic3r/Layer.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 67959a508a..0a80c019ae 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -159,6 +159,12 @@ bool Layer::is_perimeter_compatible(const PrintRegion& a, const PrintRegion& b) && config.detect_thin_wall == other_config.detect_thin_wall && config.infill_wall_overlap == other_config.infill_wall_overlap && config.top_bottom_infill_wall_overlap == other_config.top_bottom_infill_wall_overlap + // Orca: these flags directly change the effective wall count produced by the perimeter + // generator. If two regions disagree on any of them, merging their slices into one shared make_perimeters + // call would silently use the first region's flag for both. + && config.only_one_wall_first_layer == other_config.only_one_wall_first_layer + && config.only_one_wall_top == other_config.only_one_wall_top + && config.min_width_top_surface == other_config.min_width_top_surface && config.seam_slope_type == other_config.seam_slope_type && config.seam_slope_conditional == other_config.seam_slope_conditional && config.scarf_angle_threshold == other_config.scarf_angle_threshold From ddeaa4ba82e529755cb9ef7024206d8308adb3f4 Mon Sep 17 00:00:00 2001 From: CSLRDoesntGameDev <126827743+CSLRDoesntGameDev@users.noreply.github.com> Date: Mon, 18 May 2026 02:57:24 -0600 Subject: [PATCH 049/117] Add patches to the Anycubic Kobra X machine json (#13677) add patched anycubic kobra x machine json Co-authored-by: SoftFever --- .../profiles/Anycubic/machine/Anycubic Kobra X 0.4 nozzle.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/profiles/Anycubic/machine/Anycubic Kobra X 0.4 nozzle.json b/resources/profiles/Anycubic/machine/Anycubic Kobra X 0.4 nozzle.json index a33b6ee6f8..72e5707665 100644 --- a/resources/profiles/Anycubic/machine/Anycubic Kobra X 0.4 nozzle.json +++ b/resources/profiles/Anycubic/machine/Anycubic Kobra X 0.4 nozzle.json @@ -41,7 +41,7 @@ "before_layer_change_gcode": "", "best_object_pos": "0.5,0.5", "change_extrusion_role_gcode": "", - "change_filament_gcode": "; FLUSH_START\n;@2026-04-03 17:46:45 3+n换料gcode,包括7=3+4,19=3+4×4\n;_GP_INLINE_ESTIMATED_PRINTING_TIME_PLACEHOLDER\n;;; SET_VELOCITY_LIMIT VELOCITY=350 ACCEL=10000\n;;; G1 E-2 F4800\n;;; G1 Z{toolchange_z+0.6} F1800\n;;; M106 S0\n;;; G1 X265 F21000\n;;; M400 P370\n;;; G1 X277.5 F600\n;;; G1 E-2 F300\n;;; M400 P361\n;;; G1 Z{toolchange_z+3} F1200\n;;; G1 X0 F21000\n;;; G1 X-17.5 F5250\n;;; M400 P1569\n\n{local minimal_extrude_ = 5.0}\n{if one_of(filament_type[current_extruder], \"TPU\", \"PVA\")}\n{local minimal_extrude_ = 8.0}\n{endif}\n;;; G1 E{minimal_extrude_} F300\n;;; G1 E-33 F600\n\n{ local curr_t = current_extruder; local next_t = next_extruder;}\n{ local tab_step_time_ = (0, 850, 1350, 850, 850, 0, 1350, 1350, 1350, 1350, 0, 850, 850, 1350, 850, 0)};\n{ if (0 <= curr_t && curr_t < 3) then local step_from_=curr_t else local step_from_=3 endif}; from {step_from_}\n{ if (0 <= next_t && next_t < 3) then local step_into_=next_t else local step_into_=3 endif}; from {step_into_}\n;;; M400 P{tab_step_time_[step_from_ * 4 + step_into_]}\n\n{ local magic_mask_with_box = flush_length_4}\n{ if magic_mask_with_box == -1392 && size(filament_type) > 0 && size(filament_type) == size(ace_t_box_vector) && size(filament_type) == size(ace_t_slot_vector) }\n { local is_curr_in_box = size(ace_t_box_vector) > curr_t ? ace_t_box_vector[curr_t] >= 0 ? true : false : false}; is_curr_in_box = {is_curr_in_box}\n { local is_next_in_box = size(ace_t_box_vector) > next_t ? ace_t_box_vector[next_t] >= 0 ? true : false : false}; is_next_in_box = {is_next_in_box}\n\n; curr_t box={ace_t_box_vector[curr_t]}, slot={ace_t_slot_vector[curr_t]}; next_t box={ace_t_box_vector[next_t]}, slot={ace_t_slot_vector[next_t]}\n { if ace_t_box_vector[curr_t] < 0 && ace_t_box_vector[next_t] < 0}\n;;; M400 P0 ; 料架→料架\n { elsif ace_t_box_vector[curr_t] < 0 && ace_t_box_vector[next_t] >= 0 }\n;;; M400 P40409 ; 料架→盒子\n { elsif ace_t_box_vector[curr_t] >= 0 && ace_t_box_vector[next_t] < 0 }\n;;; M400 P51591 ; 盒子→料架\n { elsif ace_t_box_vector[curr_t] >= 0 && ace_t_box_vector[next_t] == ace_t_box_vector[curr_t] }\n { if 0 < ace_t_slot_vector[curr_t] && ace_t_slot_vector[curr_t] <= 4 && 0 < ace_t_slot_vector[next_t] && ace_t_slot_vector[next_t] <= 4}\n;;; M400 P2700 ; 四进四→料盒\n { else }\n;;; M400 P91279 ; 盒子→盒子\n { endif }\n { elsif ace_t_box_vector[curr_t] >= 0 && ace_t_box_vector[next_t] != ace_t_box_vector[curr_t] }\n;;; M400 P93746 ; 盒子→不同盒子\n { else }\n; 未知\n { endif }\n{ endif }\n\n\nT[next_extruder]\n\n;;; G1 E8 F300\n;;; M400 P3643\n;;; G1 E13 F1200\n;;; M400 P1000\n\n{local flush_length_= flush_length}\n{local loops_=max(1,int((flush_length_-70+150) / 150))}\n{local extrude_length_=flush_length_ / loops_}\n{local EXTRUDE_SPEED_ = 5 * 60}\n{local UNWIND_SPEED_ = 20 *60}\n{local index_ = 0}\n\n{ if (loops_ > 0) }\n{ local loops_ = loops_ - 1}\n{ local index_ = index_ + 1}\n; {index_} + {loops_}\n;;; M106 S0\n;;; M400 P1000\n;;; G1 E{extrude_length_*0.18} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.02} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.18} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.02} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.18} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.02} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.18} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.02} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.18} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.02} F{EXTRUDE_SPEED_}\n;;; M400 P{extrude_length_/0.03}\n;;; G1 E0.000001\n;;; M106 S255\n;;; M400 P2005\n;;; G1 E-2 F{UNWIND_SPEED_}\n;;; M400 P414\n{endif}\n{ if (loops_ > 0) }\n{ local loops_ = loops_ - 1}\n{ local index_ = index_ + 1}\n; {index_} + {loops_}\n;;; M106 S0\n;;; M400 P1000\n;;; G1 E{extrude_length_*0.18} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.02} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.18} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.02} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.18} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.02} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.18} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.02} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.18} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.02} F{EXTRUDE_SPEED_}\n;;; M400 P{extrude_length_/0.03}\n;;; M106 S255\n;;; M400 P2005\n;;; G1 E-2 F{UNWIND_SPEED_}\n;;; M400 P414\n{endif}\n{ if (loops_ > 0) }\n{ local loops_ = loops_ - 1}\n{ local index_ = index_ + 1}\n; {index_} + {loops_}\n;;; M106 S0\n;;; M400 P1000\n;;; G1 E{extrude_length_*0.18} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.02} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.18} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.02} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.18} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.02} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.18} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.02} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.18} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.02} F{EXTRUDE_SPEED_}\n;;; M400 P{extrude_length_/0.03}\n;;; M106 S255\n;;; M400 P2005\n;;; G1 E-2 F{UNWIND_SPEED_}\n;;; M400 P414\n{endif}\n\n; G1 X{x_after_toolchange} Y{y_after_toolchange} F12000\n; G1 Z{toolchange_z} F1200\n;;; G1 E2 F1800\n; SET_VELOCITY_LIMIT VELOCITY=450 ACCEL=10000\n;;; M400 P0\n;_GP_INLINE_ESTIMATED_PRINTING_TIME_PLACEHOLDER\n; FLUSH_END", + "change_filament_gcode": "; FLUSH_START\n;@2026-04-03 17:46:45 3+n换料gcode,包括7=3+4,19=3+4×4\n;_GP_INLINE_ESTIMATED_PRINTING_TIME_PLACEHOLDER\n;;; SET_VELOCITY_LIMIT VELOCITY=350 ACCEL=10000\n;;; G1 E-2 F4800\n;;; G1 Z{toolchange_z+0.6} F1800\n;;; M106 S0\n;;; G1 X265 F21000\n;;; M400 P370\n;;; G1 X277.5 F600\n;;; G1 E-2 F300\n;;; M400 P361\n;;; G1 Z{toolchange_z+3} F1200\n;;; G1 X0 F21000\n;;; G1 X-17.5 F5250\n;;; M400 P1569\n\n{local minimal_extrude_ = 5.0}\n{if one_of(filament_type[current_extruder], \"TPU\", \"PVA\")}\n{local minimal_extrude_ = 8.0}\n{endif}\n;;; G1 E{minimal_extrude_} F300\n;;; G1 E-33 F600\n\n{ local curr_t = current_extruder; local next_t = next_extruder;}\n{ local tab_step_time_ = (0, 850, 1350, 850, 850, 0, 1350, 1350, 1350, 1350, 0, 850, 850, 1350, 850, 0)};\n{ if (0 <= curr_t && curr_t < 3) then local step_from_=curr_t else local step_from_=3 endif}; from {step_from_}\n{ if (0 <= next_t && next_t < 3) then local step_into_=next_t else local step_into_=3 endif}; from {step_into_}\n;;; M400 P{tab_step_time_[step_from_ * 4 + step_into_]}\n\n{ local magic_mask_with_box = flush_length_4}\n{ if magic_mask_with_box == -1392 }\n { local is_curr_in_box = size(ace_t_box_vector) > curr_t ? ace_t_box_vector[curr_t] >= 0 ? true : false : false}; is_curr_in_box = {is_curr_in_box}\n { local is_next_in_box = size(ace_t_box_vector) > next_t ? ace_t_box_vector[next_t] >= 0 ? true : false : false}; is_next_in_box = {is_next_in_box}\n\n; curr_t box={ace_t_box_vector[curr_t]}, slot={ace_t_slot_vector[curr_t]}; next_t box={ace_t_box_vector[next_t]}, slot={ace_t_slot_vector[next_t]}\n { if ace_t_box_vector[curr_t] < 0 && ace_t_box_vector[next_t] < 0}\n;;; M400 P0 ; 料架→料架\n { elsif ace_t_box_vector[curr_t] < 0 && ace_t_box_vector[next_t] >= 0 }\n;;; M400 P40409 ; 料架→盒子\n { elsif ace_t_box_vector[curr_t] >= 0 && ace_t_box_vector[next_t] < 0 }\n;;; M400 P51591 ; 盒子→料架\n { elsif ace_t_box_vector[curr_t] >= 0 && ace_t_box_vector[next_t] == ace_t_box_vector[curr_t] }\n { if 0 < ace_t_slot_vector[curr_t] && ace_t_slot_vector[curr_t] <= 4 && 0 < ace_t_slot_vector[next_t] && ace_t_slot_vector[next_t] <= 4}\n;;; M400 P2700 ; 四进四→料盒\n { else }\n;;; M400 P91279 ; 盒子→盒子\n { endif }\n { elsif ace_t_box_vector[curr_t] >= 0 && ace_t_box_vector[next_t] != ace_t_box_vector[curr_t] }\n;;; M400 P93746 ; 盒子→不同盒子\n { else }\n; 未知\n { endif }\n{ endif }\n\n\nT[next_extruder]\n\n;;; G1 E8 F300\n;;; M400 P3643\n;;; G1 E13 F1200\n;;; M400 P1000\n\n{local flush_length_= flush_length}\n{local loops_=max(1,int((flush_length_-70+150) / 150))}\n{local extrude_length_=flush_length_ / loops_}\n{local EXTRUDE_SPEED_ = 5 * 60}\n{local UNWIND_SPEED_ = 20 *60}\n{local index_ = 0}\n\n{ if (loops_ > 0) }\n{ local loops_ = loops_ - 1}\n{ local index_ = index_ + 1}\n; {index_} + {loops_}\n;;; M106 S0\n;;; M400 P1000\n;;; G1 E{extrude_length_*0.18} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.02} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.18} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.02} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.18} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.02} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.18} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.02} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.18} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.02} F{EXTRUDE_SPEED_}\n;;; M400 P{extrude_length_/0.03}\n;;; G1 E0.000001\n;;; M106 S255\n;;; M400 P2005\n;;; G1 E-2 F{UNWIND_SPEED_}\n;;; M400 P414\n{endif}\n{ if (loops_ > 0) }\n{ loops_ = loops_ - 1}\n{ index_ = index_ + 1}\n; {index_} + {loops_}\n;;; M106 S0\n;;; M400 P1000\n;;; G1 E{extrude_length_*0.18} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.02} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.18} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.02} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.18} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.02} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.18} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.02} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.18} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.02} F{EXTRUDE_SPEED_}\n;;; M400 P{extrude_length_/0.03}\n;;; M106 S255\n;;; M400 P2005\n;;; G1 E-2 F{UNWIND_SPEED_}\n;;; M400 P414\n{endif}\n{ if (loops_ > 0) }\n{ loops_ = loops_ - 1}\n{ index_ = index_ + 1}\n; {index_} + {loops_}\n;;; M106 S0\n;;; M400 P1000\n;;; G1 E{extrude_length_*0.18} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.02} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.18} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.02} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.18} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.02} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.18} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.02} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.18} F{EXTRUDE_SPEED_}\n;;; G1 E{extrude_length_*0.02} F{EXTRUDE_SPEED_}\n;;; M400 P{extrude_length_/0.03}\n;;; M106 S255\n;;; M400 P2005\n;;; G1 E-2 F{UNWIND_SPEED_}\n;;; M400 P414\n{endif}\n\n; G1 X{x_after_toolchange} Y{y_after_toolchange} F12000\n; G1 Z{toolchange_z} F1200\n;;; G1 E2 F1800\n; SET_VELOCITY_LIMIT VELOCITY=450 ACCEL=10000\n;;; M400 P0\n;_GP_INLINE_ESTIMATED_PRINTING_TIME_PLACEHOLDER\n; FLUSH_END", "cooling_tube_length": "0", "cooling_tube_retraction": "0", "deretraction_speed": [ From b9ff15054faea23eb7ce99f466363e7f21d44755 Mon Sep 17 00:00:00 2001 From: Thomas Henauer Date: Mon, 18 May 2026 12:01:19 +0200 Subject: [PATCH 050/117] Fix wxGTK submenu popup parenting and sizing on Wayland (#13707) --- src/slic3r/GUI/Widgets/DropDown.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/Widgets/DropDown.cpp b/src/slic3r/GUI/Widgets/DropDown.cpp index d698f9f7dc..abbecc4353 100644 --- a/src/slic3r/GUI/Widgets/DropDown.cpp +++ b/src/slic3r/GUI/Widgets/DropDown.cpp @@ -564,11 +564,6 @@ void DropDown::messureSize() subDropDown->use_content_width = true; subDropDown->Create(GetParent()); #ifdef __WXGTK__ - // Orca: Keep the wx parent as the combobox so wxPopupTransientWindow installs - // its capture handlers on the main dropdown, but make the native GTK - // popup transient for the currently open popup to satisfy Wayland's - // xdg-shell rule that a popup's parent must be the topmost mapped popup. - gtk_window_set_transient_for(GTK_WINDOW(subDropDown->GetHandle()), GTK_WINDOW(GetHandle())); // Orca: On Wayland, while the sub holds an xdg_popup grab, motion events for // the cursor over main may not be delivered (Mutter drops motion // outside the grabbing surface). Poll on idle and synthesize a @@ -636,8 +631,11 @@ void DropDown::autoPosition() // may exceed auto drect = wxDisplay(GetParent()).GetGeometry(); if (GetPosition().y + size.y + 10 > drect.GetBottom()) { + int available_height = drect.GetBottom() - GetPosition().y - 10; + if (available_height < rowSize.y * 2) + return; if (use_content_width && count <= 15) size.x += 6; - size.y = drect.GetBottom() - GetPosition().y - 10; + size.y = available_height; wxWindow::SetSize(size); if (selection >= 0) { if (offset.y + rowSize.y * (selection + 1) > size.y) @@ -727,6 +725,13 @@ void DropDown::mouseMove(wxMouseEvent &event) drop.group = items[-index - 2].group_key; drop.need_sync = true; drop.messureSize(); +#ifdef __WXGTK__ + // wxGTK wraps popup contents in a native GtkWindow. Make the submenu + // transient for the currently mapped parent popup window before + // positioning/showing it, so wlroots/Hyprland sees the topmost parent. + if (m_widget && drop.m_widget) + gtk_window_set_transient_for(GTK_WINDOW(drop.m_widget), GTK_WINDOW(m_widget)); +#endif drop.autoPosition(); drop.paintNow(); if (!drop.IsShown()) From b4aa070c40dd73ddef33b49b61e37feb9d67d7bf Mon Sep 17 00:00:00 2001 From: yw4z Date: Mon, 18 May 2026 14:46:47 +0300 Subject: [PATCH 051/117] MultiChooseDialog & CheckList class & improvements for Profile Dependencies (#9971) * init * fix * fix * update * update * update * Update Tab.cpp * Update CheckList.cpp --- resources/images/filter.svg | 1 + src/slic3r/CMakeLists.txt | 4 + src/slic3r/GUI/MultiChoiceDialog.cpp | 58 +++++++ src/slic3r/GUI/MultiChoiceDialog.hpp | 37 +++++ src/slic3r/GUI/Tab.cpp | 19 ++- src/slic3r/GUI/Widgets/CheckList.cpp | 225 +++++++++++++++++++++++++++ src/slic3r/GUI/Widgets/CheckList.hpp | 59 +++++++ 7 files changed, 399 insertions(+), 4 deletions(-) create mode 100644 resources/images/filter.svg create mode 100644 src/slic3r/GUI/MultiChoiceDialog.cpp create mode 100644 src/slic3r/GUI/MultiChoiceDialog.hpp create mode 100644 src/slic3r/GUI/Widgets/CheckList.cpp create mode 100644 src/slic3r/GUI/Widgets/CheckList.hpp diff --git a/resources/images/filter.svg b/resources/images/filter.svg new file mode 100644 index 0000000000..c48942073b --- /dev/null +++ b/resources/images/filter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index c22b9b64fd..5e502207ef 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -329,6 +329,8 @@ set(SLIC3R_GUI_SOURCES GUI/Mouse3DController.hpp GUI/MsgDialog.cpp GUI/MsgDialog.hpp + GUI/MultiChoiceDialog.hpp + GUI/MultiChoiceDialog.cpp GUI/MultiMachine.cpp GUI/MultiMachine.hpp GUI/MultiMachineManagerPage.cpp @@ -496,6 +498,8 @@ set(SLIC3R_GUI_SOURCES GUI/Widgets/Button.hpp GUI/Widgets/CheckBox.cpp GUI/Widgets/CheckBox.hpp + GUI/Widgets/CheckList.cpp + GUI/Widgets/CheckList.hpp GUI/Widgets/ComboBox.cpp GUI/Widgets/ComboBox.hpp GUI/Widgets/DialogButtons.cpp diff --git a/src/slic3r/GUI/MultiChoiceDialog.cpp b/src/slic3r/GUI/MultiChoiceDialog.cpp new file mode 100644 index 0000000000..6bd5412737 --- /dev/null +++ b/src/slic3r/GUI/MultiChoiceDialog.cpp @@ -0,0 +1,58 @@ +#include "MultiChoiceDialog.hpp" + +#include "GUI_App.hpp" +#include "MainFrame.hpp" + +namespace Slic3r { namespace GUI { + +MultiChoiceDialog::MultiChoiceDialog( + wxWindow* parent, + const wxString& message, + const wxString& caption, + const wxArrayString& choices +) + : DPIDialog(parent ? parent : static_cast(wxGetApp().mainframe), wxID_ANY, caption, wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX) +{ + SetBackgroundColour(*wxWHITE); + + wxBoxSizer* w_sizer = new wxBoxSizer(wxVERTICAL); + + if(!message.IsEmpty()){ + wxStaticText *msg = new wxStaticText(this, wxID_ANY, message); + msg->SetFont(Label::Body_13); + msg->Wrap(-1); + w_sizer->Add(msg, 0, wxRIGHT | wxLEFT | wxTOP, FromDIP(10)); + } + + m_check_list = new CheckList(this, choices); + + w_sizer->Add(m_check_list, 1, wxRIGHT | wxLEFT | wxTOP | wxEXPAND, FromDIP(10)); + + auto dlg_btns = new DialogButtons(this, {"OK", "Cancel"}); + + dlg_btns->GetOK()->Bind( wxEVT_BUTTON, [this](wxCommandEvent &e) {EndModal(wxID_OK);}); + dlg_btns->GetCANCEL()->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) {EndModal(wxID_CANCEL);}); + + w_sizer->Add(dlg_btns, 0, wxEXPAND); + + SetSizer(w_sizer); + Layout(); + w_sizer->Fit(this); + wxGetApp().UpdateDlgDarkUI(this); +} + +wxArrayInt MultiChoiceDialog::GetSelections() const +{ + return m_check_list->GetSelections(); +} + +void MultiChoiceDialog::SetSelections(wxArrayInt sel_array) +{ + m_check_list->SetSelections(sel_array); +} + +MultiChoiceDialog::~MultiChoiceDialog() {} + +void MultiChoiceDialog::on_dpi_changed(const wxRect &suggested_rect) {} + +}} // namespace Slic3r::GUI \ No newline at end of file diff --git a/src/slic3r/GUI/MultiChoiceDialog.hpp b/src/slic3r/GUI/MultiChoiceDialog.hpp new file mode 100644 index 0000000000..f636bebd3b --- /dev/null +++ b/src/slic3r/GUI/MultiChoiceDialog.hpp @@ -0,0 +1,37 @@ +#ifndef slic3r_GUI_MultiChoiceDialog_hpp_ +#define slic3r_GUI_MultiChoiceDialog_hpp_ + +#include "Widgets/CheckList.hpp" +#include "Widgets/DialogButtons.hpp" + +#include +#include +#include + + +namespace Slic3r { namespace GUI { + +class MultiChoiceDialog : public DPIDialog +{ +public: + MultiChoiceDialog( + wxWindow* parent = nullptr, + const wxString& message = wxEmptyString, + const wxString& caption = wxEmptyString, + const wxArrayString& choices = wxArrayString() + ); + ~MultiChoiceDialog(); + + wxArrayInt GetSelections() const; + + void SetSelections(wxArrayInt sel_array); + +protected: + CheckList* m_check_list; + wxArrayInt m_selected_indices; + void on_dpi_changed(const wxRect &suggested_rect) override; +}; + +}} // namespace Slic3r::GUI + +#endif \ No newline at end of file diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index ccf595b22a..44b34e590e 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -40,7 +40,7 @@ #include "UnsavedChangesDialog.hpp" #include "SavePresetDialog.hpp" #include "EditGCodeDialog.hpp" - +#include "MultiChoiceDialog.hpp" #include "MsgDialog.hpp" #include "Notebook.hpp" @@ -7002,7 +7002,15 @@ wxSizer* Tab::compatible_widget_create(wxWindow* parent, PresetDependencies &dep presets.Add(from_u8(preset.name)); } - wxMultiChoiceDialog dlg(parent, deps.dialog_title, deps.dialog_label, presets); + if(deps.type == Preset::TYPE_PRINTER){ + deps.dialog_title = "Compatible printers"; + deps.dialog_label = "Select printers"; + }else{ + deps.dialog_title = "Compatible process profiles"; + deps.dialog_label = "Select profiles"; + } + + MultiChoiceDialog dlg(parent, deps.dialog_label, deps.dialog_title, presets); wxGetApp().UpdateDlgDarkUI(&dlg); // Collect and set indices of depending_presets marked as compatible. wxArrayInt selections; @@ -7015,13 +7023,16 @@ wxSizer* Tab::compatible_widget_create(wxWindow* parent, PresetDependencies &dep break; } dlg.SetSelections(selections); + dlg.SetSize(FromDIP(wxSize(360, 480))); std::vector value; // Show the dialog. if (dlg.ShowModal() == wxID_OK) { selections.Clear(); selections = dlg.GetSelections(); - for (auto idx : selections) - value.push_back(presets[idx].ToUTF8().data()); + // leave list empty if all items checked. this will check "All" checkbox automatically. also fixes unnecessary config change + if(selections.GetCount() != presets.GetCount()) + for (auto idx : selections) + value.push_back(presets[idx].ToUTF8().data()); if (value.empty()) { deps.checkbox->SetValue(1); deps.btn->Disable(); diff --git a/src/slic3r/GUI/Widgets/CheckList.cpp b/src/slic3r/GUI/Widgets/CheckList.cpp new file mode 100644 index 0000000000..cd0dcebff3 --- /dev/null +++ b/src/slic3r/GUI/Widgets/CheckList.cpp @@ -0,0 +1,225 @@ +#include "CheckList.hpp" + +#include "slic3r/GUI/GUI_App.hpp" + +CheckList::CheckList( + wxWindow* parent, + const wxArrayString& choices, + long scroll_style +) + : wxWindow(parent, wxID_ANY) + , m_search(this, "search", 16) + , m_menu(this, "filter", 16) + , m_first_load(true) +{ + Freeze(); + w_sizer = new wxBoxSizer(wxVERTICAL); + + f_sizer = new wxBoxSizer(wxHORIZONTAL); + f_bar = new wxPanel(this, wxID_ANY); + f_bar->SetBackgroundColour(parent->GetBackgroundColour()); + m_filter_box = new TextInput(f_bar, "", "", "", wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER); + m_filter_box->SetIcon(m_search.bmp()); + m_filter_box->SetMinSize(FromDIP(wxSize(200,24))); + m_filter_box->SetSize(FromDIP(wxSize(-1,24))); + m_filter_box->SetFocus(); + m_filter_ctrl = m_filter_box->GetTextCtrl(); + m_filter_ctrl->SetFont(Label::Body_13); + m_filter_ctrl->SetSize(wxSize(-1, FromDIP(16))); // Centers text vertically + m_filter_ctrl->SetHint(_L("Type to filter...")); + m_filter_ctrl->Bind(wxEVT_TEXT, [this](auto &e) {Filter(m_filter_ctrl->GetValue());}); + m_filter_ctrl->Bind(wxEVT_TEXT_ENTER, [this](auto &e) {Filter(m_filter_ctrl->GetValue());}); + m_filter_ctrl->Bind(wxEVT_SET_FOCUS, [this](auto &e) {Filter(m_filter_ctrl->GetValue());e.Skip();}); + m_filter_ctrl->Bind(wxEVT_KILL_FOCUS, [this](auto &e) {Filter(m_filter_ctrl->GetValue());e.Skip();}); + f_sizer->Add(m_filter_box, 1, wxEXPAND); + Bind(wxEVT_SET_FOCUS, [this](auto &e) {m_filter_box->SetFocus();}); + + fb_sizer = new wxBoxSizer(wxHORIZONTAL); + auto create_btn = [this] (wxString title, bool select){ + auto btn = new wxStaticText(f_bar, wxID_ANY, title); + btn->SetForegroundColour("#009687"); + btn->SetCursor(wxCURSOR_HAND); + btn->SetFont(Label::Body_13); + btn->Bind(wxEVT_LEFT_DOWN, [this, select](wxMouseEvent &e) {SelectAll(select);}); + fb_sizer->Add(btn, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + }; + f_sizer->Add(fb_sizer,0 ,wxALIGN_CENTER_VERTICAL); + create_btn(_L("All") , true); + create_btn(_L("None"), false); + + m_menu_button = new wxStaticBitmap(f_bar, wxID_ANY, m_menu.bmp()); + m_menu_button->SetCursor(wxCURSOR_HAND); + m_menu_button->Bind(wxEVT_LEFT_DOWN, &CheckList::ShowMenu, this); + f_sizer->Add(m_menu_button,0 ,wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + f_bar->SetSizerAndFit(f_sizer); + w_sizer->Add(f_bar, 0, wxEXPAND); + + auto spacer = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, FromDIP(3))); + spacer->SetBackgroundColour(parent->GetBackgroundColour()); + w_sizer->Add(spacer, 0, wxEXPAND); + + s_sizer = new wxBoxSizer(wxVERTICAL); + m_scroll_area = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, scroll_style); + m_scroll_area->SetScrollRate(0, 10); + m_scroll_area->SetSizer(s_sizer); + m_scroll_area->SetBackgroundColour(parent->GetBackgroundColour()); + m_scroll_area->Bind(wxEVT_RIGHT_DOWN, &CheckList::ShowMenu, this); + m_scroll_area->DisableFocusFromKeyboard(); + + m_info = new wxStaticText(m_scroll_area, wxID_ANY, ""); + m_info->SetFont(Label::Body_13); + s_sizer->Add(m_info, 1, wxALIGN_CENTER_HORIZONTAL | wxALL, FromDIP(10)); + m_info->Hide(); + + m_info_nonsel = _L("No selected items..."); + m_info_allsel = _L("All items selected..."); + m_info_empty = _L("No matching items..."); + + SetBackgroundColour(StateColor::darkModeColorFor("#DBDBDB")); // draws border on wxScrolledWindow + + w_sizer->Add(m_scroll_area, 1, wxEXPAND | wxALL, FromDIP(1)); // 1 for border + s_sizer->Layout(); + + m_list_size = choices.size(); + + m_checks.reserve(m_list_size); + + auto margin = FromDIP(2); + wxCheckBox* cb; + + for (size_t i = 0; i < m_list_size; ++i){ + cb = new wxCheckBox(m_scroll_area, wxID_ANY, choices[i]); + m_checks.emplace_back(cb); + s_sizer->Add(cb, 0, wxALL, margin); + } + + m_scroll_area->FitInside(); + s_sizer->Layout(); + + SetSizer(w_sizer); + Layout(); + Thaw(); +} + +void CheckList::SetSelections(wxArrayInt sel_array){ + if(!m_first_load){ + for (size_t i = 0; i < m_list_size; ++i) + Check(i, false); + m_first_load = false; + } + + for (int i : sel_array) + Check(i, true); +} + +wxArrayInt CheckList::GetSelections() +{ + wxArrayInt checks; + for (size_t i = 0; i < m_list_size; ++i) + if (m_checks[i]->GetValue()) + checks.push_back(i); + return checks; +} + +void CheckList::Check(int i, bool checked) +{ + if (i > -1 && i < m_list_size) + m_checks[i]->SetValue(checked); +} + +void CheckList::SelectAll(bool value) +{ + for (size_t i = 0; i < m_list_size; ++i) + Check(i, value); +} + +void CheckList::SelectVisible(bool value) +{ + auto filter = m_filter_ctrl->GetValue().Lower(); + if((!value && filter == "::unsel") || (value && filter == "::sel")) + m_filter_ctrl->SetValue(""); + for (size_t i = 0; i < m_list_size; ++i) + if(m_checks[i]->IsShown()) + Check(i, value); +} + +bool CheckList::IsChecked(int i) +{ + if (i > -1 && i < m_list_size) + return false; + return m_checks[i]->GetValue(); +} + +void CheckList::Filter(const wxString& filterText) +{ + Freeze(); + auto filter = filterText.Lower(); + auto c_text = m_filter_ctrl->GetValue().Lower(); + + if(filter == "::sel" || filter == "::nonsel"){ + if(c_text != filter){ // not text input + m_filter_ctrl->SetValue(filter); + m_filter_ctrl->SetSelection(0,-1); + } + fb_sizer->Show(false); + if (filter == "::sel") + for (auto& cb : m_checks) cb->Show(cb->GetValue()); + else + for (auto& cb : m_checks) cb->Show(!cb->GetValue()); + } + else{ + bool clear = filterText.IsEmpty(); + fb_sizer->Show(clear); + for (auto& cb : m_checks) + cb->Show(clear || cb->GetLabel().Lower().Contains(filter)); + } + + m_info->Show(); + for (size_t i = 0; i < m_list_size; ++i) { + if (m_checks[i]->IsShown()){ + m_info->Hide(); + break; + } + } + if (m_info->IsShown()) + m_info->SetLabel(filter == "::sel" ? m_info_nonsel : filter == "::nonsel" ? m_info_allsel : m_info_empty); + + m_scroll_area->FitInside(); + f_sizer->Layout(); + Thaw(); +} + +void CheckList::ShowMenu(wxMouseEvent &evt) +{ + bool filtering = !m_filter_ctrl->GetValue().IsEmpty(); + bool list_empty = m_info->IsShown(); + + wxMenu m; + m.Append(wxID_FILE1, _L("Select All" ))->Enable(!filtering); + m.Append(wxID_FILE2, _L("Deselect All"))->Enable(!filtering); + m.AppendSeparator(); + m.Append(wxID_FILE3, _L("Select visible" ))->Enable(!list_empty && filtering); + m.Append(wxID_FILE4, _L("Deselect visible"))->Enable(!list_empty && filtering); + m.AppendSeparator(); + m.Append(wxID_FILE5, _L("Filter selected" )); + m.Append(wxID_FILE6, _L("Filter nonSelected")); + + m.Bind(wxEVT_MENU, [this](wxCommandEvent& e) { + switch (e.GetId()){ + case wxID_FILE1: SelectAll(true) ; break; + case wxID_FILE2: SelectAll(false) ; break; + case wxID_FILE3: SelectVisible(true) ; break; + case wxID_FILE4: SelectVisible(false); break; + case wxID_FILE5: Filter("::sel") ; break; + case wxID_FILE6: Filter("::nonsel") ; break; + default: break; + } + },wxID_FILE1, wxID_FILE6); + + wxWindow* src = dynamic_cast(evt.GetEventObject()); + if (!src) return; + wxPoint screen_pos = src->ClientToScreen(evt.GetPosition()); + wxPoint local_pos = ScreenToClient(screen_pos); + PopupMenu(&m, local_pos); +} \ No newline at end of file diff --git a/src/slic3r/GUI/Widgets/CheckList.hpp b/src/slic3r/GUI/Widgets/CheckList.hpp new file mode 100644 index 0000000000..bd40e1ad84 --- /dev/null +++ b/src/slic3r/GUI/Widgets/CheckList.hpp @@ -0,0 +1,59 @@ +#ifndef slic3r_GUI_CHECKLIST_hpp_ +#define slic3r_GUI_CHECKLIST_hpp_ + +#include "../wxExtensions.hpp" + +#include "Label.hpp" +#include "TextInput.hpp" + +#include +#include +#include +#include + +class CheckList : public wxWindow +{ +public: + CheckList( + wxWindow* parent, + const wxArrayString& choices = wxArrayString(), + long scroll_style = wxVSCROLL + ); + + wxArrayInt GetSelections(); + void SetSelections(wxArrayInt sel_array); + void Check(int i, bool checked); + bool IsChecked(int i); + + void Filter(const wxString& filterText); + void SelectAll(bool value); + void SelectVisible(bool value); + +private: + void ShowMenu(wxMouseEvent &e); + + std::vector m_checks; + int m_list_size; + bool m_first_load; + wxBoxSizer* w_sizer; + + wxPanel* f_bar; + wxBoxSizer* f_sizer; + TextInput* m_filter_box; + wxTextCtrl* m_filter_ctrl; + wxBoxSizer* fb_sizer; + wxStaticBitmap* m_menu_button; + + wxScrolledWindow* m_scroll_area; + wxBoxSizer* s_sizer; + + wxStaticText* m_info; + wxString m_info_nonsel; + wxString m_info_allsel; + wxString m_info_empty; + + ScalableBitmap m_search; + ScalableBitmap m_menu; +}; + +#endif // !slic3r_GUI_CheckList_hpp_ From 248a55abd0864a7c76b5d1466b7558c5abb4ad62 Mon Sep 17 00:00:00 2001 From: Kappa971 <62349018+Kappa971@users.noreply.github.com> Date: Mon, 18 May 2026 15:29:38 +0200 Subject: [PATCH 052/117] Update Italian translation (#13674) --- localization/i18n/it/OrcaSlicer_it.po | 734 +++++++++++++++++--------- 1 file changed, 483 insertions(+), 251 deletions(-) diff --git a/localization/i18n/it/OrcaSlicer_it.po b/localization/i18n/it/OrcaSlicer_it.po index 991fe0c0fa..91f171c11d 100644 --- a/localization/i18n/it/OrcaSlicer_it.po +++ b/localization/i18n/it/OrcaSlicer_it.po @@ -533,7 +533,7 @@ msgid "Drag" msgstr "Trascina" msgid "Move cut line" -msgstr "" +msgstr "Sposta linea di taglio" msgid "Draw cut line" msgstr "Disegna linea di taglio" @@ -831,7 +831,7 @@ msgid "Embossing actions" msgstr "Azioni di rilievo" msgid "Position on surface" -msgstr "" +msgstr "Posizione sulla superficie" msgid "Emboss" msgstr "Rilievo" @@ -1911,14 +1911,14 @@ msgstr "Aggiornamento dell'informativa sulla riservatezza" #, c-format, boost-format msgid "your Bambu Cloud profile (user ID: \"%s\")" -msgstr "" +msgstr "il tuo profilo Bambu Cloud (ID utente: \"%s\")" msgid "your default profile" -msgstr "" +msgstr "il tuo profilo predefinito" #, c-format, boost-format msgid "a user profile (folder: \"%s\")" -msgstr "" +msgstr "profilo utente (cartella: \"%s\")" #, c-format, boost-format msgid "" @@ -1926,15 +1926,20 @@ msgid "" "Do you want to migrate them to your OrcaCloud profile?\n" "This will copy your presets so they are available under your new account." msgstr "" +"Sono stati trovati dei profili utente in %s.\n" +"Desideri migrarli al tuo profilo OrcaCloud?\n" +"In questo modo verranno copiati e resi disponibili nel tuo nuovo profilo." msgid "Migrate User Presets" -msgstr "" +msgstr "Migra profili utente" #, c-format, boost-format msgid "" "Failed to migrate user presets:\n" "%s" msgstr "" +"Impossibile migrare i profili utente:\n" +"%s" msgid "" "The number of user presets cached in the cloud has exceeded the upper limit, " @@ -1951,29 +1956,33 @@ msgid "" "reduce the preset size by removing custom configurations or use it locally " "only." msgstr "" +"Il contenuto del profilo è troppo grande per essere sincronizzato con il " +"cloud (supera 1 MB). Si prega di ridurre le dimensioni del profilo " +"rimuovendo le configurazioni personalizzate oppure utilizzarlo solo in " +"locale." #, c-format, boost-format msgid "%s updated from %s to %s" -msgstr "" +msgstr "%s aggiornato da %s a %s" #, c-format, boost-format msgid "%s has been downloaded." -msgstr "" +msgstr "%s è stato scaricato." #, c-format, boost-format msgid "Bundle %s is no longer available." -msgstr "" +msgstr "Il pacchetto %s non è più disponibile." #, c-format, boost-format msgid "Bundle %s access is unauthorized." -msgstr "" +msgstr "L'accesso al pacchetto %s non è autorizzato." msgid "Loading user preset" msgstr "Caricamento profili utente" #, c-format, boost-format msgid "%s has been removed." -msgstr "" +msgstr "%s è stato rimosso." msgid "Switching application language" msgstr "Cambio lingua applicazione" @@ -2141,7 +2150,7 @@ msgid "Backspace" msgstr "Backspace" msgid "Load..." -msgstr "Caricamento..." +msgstr "Carica..." msgid "Cube" msgstr "Cubo" @@ -2190,9 +2199,9 @@ msgid "" "No - Do not change these settings for me" msgstr "" "Questo modello presenta una goffratura del testo sulla superficie superiore. " -"Per ottenere risultati ottimali, si consiglia di impostare la 'Soglia di una " -"parete(min_width_top_surface)' su 0 affinché la 'Soglia di una sola parete " -"sulle superfici superiori' funzioni al meglio.\n" +"Per ottenere risultati ottimali, si consiglia di impostare 'Soglia singola " +"parete (min_width_top_surface)' su 0 affinché l'opzione 'Solo una parete su " +"superfici superiori' funzioni al meglio.\n" "Sì - Modificare automaticamente queste impostazioni\n" "No - Non modificare queste impostazioni per me" @@ -2221,7 +2230,7 @@ msgid "Support Enforcer" msgstr "Supporto di rinforzo" msgid "Change part type" -msgstr "" +msgstr "Modifica tipo di parte" msgid "Set as an individual object" msgstr "Imposta come singolo oggetto" @@ -2239,10 +2248,10 @@ msgid "Printable" msgstr "Stampabile" msgid "Auto Drop" -msgstr "" +msgstr "Rilascio automatico" msgid "Automatically drops the selected object to the build plate" -msgstr "" +msgstr "Rilascia automaticamente l'oggetto selezionato sul piano di stampa" msgid "Fix model" msgstr "Correggi il modello" @@ -2427,10 +2436,10 @@ msgid "Select all objects on the current plate" msgstr "Seleziona tutti gli oggetti sul piatto corrente" msgid "Select All Plates" -msgstr "Seleziona tutte le piastre" +msgstr "Seleziona tutti i piatti" msgid "Select all objects on all plates" -msgstr "Seleziona tutti gli oggetti su tutte le piastre" +msgstr "Seleziona tutti gli oggetti su tutti i piatti" msgid "Delete All" msgstr "Elimina tutto" @@ -2493,7 +2502,7 @@ msgid "Simplify Model" msgstr "Semplifica Modello" msgid "Subdivision mesh" -msgstr "Suddivisione mesh" +msgstr "Suddivisione maglia" msgid "(Lost color)" msgstr "(Colore perso)" @@ -2505,7 +2514,7 @@ msgid "Drop" msgstr "Lascia" msgid "Edit Process Settings" -msgstr "Modifica le impostazioni del processo" +msgstr "Modifica impostazioni di processo" msgid "Copy Process Settings" msgstr "Copia impostazioni di processo" @@ -2523,7 +2532,7 @@ msgid "Set Filament for selected items" msgstr "Imposta filamento per gli elementi selezionati" msgid "Automatically snaps the selected object to the build plate" -msgstr "" +msgstr "Aggancia automaticamente l'oggetto selezionato sul piano di stampa" msgid "Unlock" msgstr "Sblocca" @@ -2821,7 +2830,7 @@ msgid "Brim" msgstr "Tesa" msgid "Object/Part Settings" -msgstr "" +msgstr "Impostazioni oggetto/parte" msgid "Reset parameter" msgstr "Ripristina parametro" @@ -3535,6 +3544,14 @@ msgid "" "enhancements. Each project carried the work of its predecessors forward, " "crediting those who came before." msgstr "" +"I programmi di sezionamento a sorgente aperta per l'elaborazione del modelli " +"per la stampa 3D, si fondano su una tradizione di collaborazione e " +"riconoscimento del merito. Slic3r, creato da Alessandro Ranellucci e dalla " +"comunità RepRap, ha gettato le basi. PrusaSlicer di Prusa Research ha " +"sviluppato ulteriormente questo lavoro; Bambu Studio è derivato da " +"PrusaSlicer e SuperSlicer lo ha ampliato con miglioramenti apportati dalla " +"comunità. Ogni progetto ha portato avanti il ​​lavoro dei suoi predecessori, " +"riconoscendo il contributo di chi li ha preceduti." msgid "" "OrcaSlicer began in that same spirit, drawing from PrusaSlicer, BambuStudio, " @@ -3542,12 +3559,20 @@ msgid "" "introducing advanced calibration tools, precise wall and seam control and " "hundreds of other features." msgstr "" +"OrcaSlicer è nato con lo stesso spirito, traendo ispirazione da PrusaSlicer, " +"BambuStudio, SuperSlicer e CuraSlicer. Ma da allora si è evoluto ben oltre " +"le sue origini, introducendo strumenti di calibrazione avanzati, controllo " +"preciso su pareti e cuciture e centinaia di altre funzionalità." msgid "" "Today, OrcaSlicer is the most widely used and actively developed open-source " "slicer in the 3D printing community. Many of its innovations have been " "adopted by other slicers, making it a driving force for the entire industry." msgstr "" +"Oggi, OrcaSlicer è il programma di sezionamento a sorgente aperta più " +"utilizzato e attivamente sviluppato nella comunità della stampa 3D. Molte " +"delle sue innovazioni sono state adottate da altri programmi di stampa, " +"rendendolo un punto di riferimento per l'intero settore." msgid "Version" msgstr "Versione" @@ -4501,7 +4526,7 @@ msgid "Inspecting first layer" msgstr "Ispezione del primo strato" msgid "Identifying build plate type" -msgstr "Identificazione tipo piatto di stampa" +msgstr "Identificazione tipo piano di stampa" msgid "Calibrating Micro Lidar" msgstr "Calibrazione Micro Lidar" @@ -4970,7 +4995,7 @@ msgid "Acceleration" msgstr "Accelerazione" msgid "Jerk" -msgstr "" +msgstr "Scatto" msgid "Fan Speed" msgstr "Velocità ventola" @@ -5120,10 +5145,10 @@ msgid "Color: " msgstr "Colore: " msgid "Acceleration: " -msgstr "" +msgstr "Accelerazione: " msgid "Jerk: " -msgstr "" +msgstr "Scatto: " msgid "PA: " msgstr "PA: " @@ -5156,8 +5181,9 @@ msgid "" "Automatically re-slice according to the optimal filament grouping, and the " "grouping results will be displayed after slicing." msgstr "" -"Ri-slicing automatico secondo il raggruppamento filamenti ottimale; i " -"risultati del raggruppamento verranno visualizzati dopo lo slicing." +"Rielabora automaticamente in base al raggruppamento ottimale dei filamenti; " +"i risultati del raggruppamento verranno visualizzati al termine " +"dell'elaborazione." msgid "Filament Grouping" msgstr "Raggruppamento filamenti" @@ -5258,10 +5284,10 @@ msgid "Actual Speed (mm/s)" msgstr "Velocità effettiva (mm/s)" msgid "Acceleration (mm/s²)" -msgstr "" +msgstr "Accelerazione (mm/s²)" msgid "Jerk (mm/s)" -msgstr "" +msgstr "Scatto (mm/s)" msgid "Fan Speed (%)" msgstr "Velocità ventola (%)" @@ -5294,7 +5320,7 @@ msgid "Filament change times" msgstr "Tempi cambio filamento" msgid "Tool changes" -msgstr "" +msgstr "Cambi testina" msgid "Color change" msgstr "Cambio colore" @@ -5387,10 +5413,10 @@ msgid "Sequence" msgstr "Sequenza" msgid "Object Selection" -msgstr "" +msgstr "Selezione oggetto" msgid "Part Selection" -msgstr "" +msgstr "Selezione parte" msgid "number keys" msgstr "tasti numerici" @@ -5582,7 +5608,7 @@ msgid "Paint Toolbar" msgstr "Barra strumenti di pittura" msgid "part selection" -msgstr "" +msgstr "selezione parte" msgid "Explosion Ratio" msgstr "Rapporto di esplosione" @@ -6136,7 +6162,7 @@ msgid "View" msgstr "Vista" msgid "Preset Bundle" -msgstr "" +msgstr "Pacchetto profili" msgid "Help" msgstr "Aiuto" @@ -6178,7 +6204,7 @@ msgid "VFA" msgstr "VFA" msgid "Calibration Guide" -msgstr "" +msgstr "Guida alla calibrazione" msgid "&Open G-code" msgstr "Apri G-code" @@ -6293,6 +6319,11 @@ msgid "" "2. The Filament presets\n" "3. The Printer presets" msgstr "" +"Desideri sincronizzare i tuoi dati personali da Orca Cloud?\n" +"Contiene le seguenti informazioni:\n" +"1. Profili di processo\n" +"2. Profili di filamento\n" +"3. Profili della stampante" msgid "Synchronization" msgstr "Sincronizzazione" @@ -6314,6 +6345,8 @@ msgid "" "The player is not loaded because the GStreamer GTK video sink is missing or " "failed to initialize." msgstr "" +"Il lettore non è stato caricato perché il ricevitore video GTK di GStreamer " +"è mancante o non è stato inizializzato correttamente." msgid "Please confirm if the printer is connected." msgstr "Verifica che la stampante sia collegata." @@ -7216,10 +7249,10 @@ msgid "Model file downloaded." msgstr "File del modello scaricato." msgid "Shared profiles may be available for this printer." -msgstr "" +msgstr "Per questa stampante potrebbero essere disponibili profili condivisi." msgid "Browse shared profiles" -msgstr "" +msgstr "Esplora i profili condivisi" msgid "Serious warning:" msgstr "Avviso serio:" @@ -7433,7 +7466,7 @@ msgid "Objects" msgstr "Oggetti" msgid "Cycle settings visibility" -msgstr "" +msgstr "Visibilità impostazioni" msgid "Compare presets" msgstr "Confronta i profili" @@ -7718,28 +7751,30 @@ msgid "Load 3MF" msgstr "Carica 3MF" msgid "BambuStudio Project" -msgstr "" +msgstr "Progetto BambuStudio" -#, fuzzy msgid "The 3MF is not supported by OrcaSlicer, loading geometry data only." msgstr "" "Il formato 3MF non è supportato da OrcaSlicer. Saranno caricati solo i dati " "geometrici." -#, fuzzy msgid "" "The 3MF file was generated by an old OrcaSlicer version, loading geometry " "data only." msgstr "" -"Il 3MF è stato generato da una vecchia versione di OrcaSlicer, caricando " -"solo i dati geometrici." +"Il file 3MF è stato generato da una vecchia versione di OrcaSlicer. Saranno " +"caricati solo i dati geometrici." msgid "" "The 3MF file was generated by an older version, loading geometry data only." msgstr "" +"Il file 3MF è stato generato da una versione precedente. Saranno caricati " +"solo i dati geometrici." msgid "The 3MF file was generated by BambuStudio, loading geometry data only." msgstr "" +"Il file 3MF è stato generato da BambuStudio. Saranno caricati solo i dati " +"geometrici." msgid "" "This project was created with an OrcaSlicer 2.3.1-alpha and uses infill " @@ -7782,12 +7817,18 @@ msgid "" "The 3MF was created by BambuStudio (version %s), which is newer than the " "compatible version %s. Found unrecognized settings:" msgstr "" +"Il file 3MF è stato creato da BambuStudio (versione %s), che è più recente " +"della versione compatibile %s. Sono state trovate impostazioni non " +"riconosciute:" #, c-format, boost-format msgid "" "The 3MF was created by BambuStudio (version %s), which is newer than the " "compatible version %s. Some settings may not be fully compatible." msgstr "" +"Il file 3MF è stato creato da BambuStudio (versione %s), che è più recente " +"della versione compatibile %s. Alcune impostazioni potrebbero non essere " +"pienamente compatibili." msgid "" "The 3MF was created by BambuStudio. Some settings may differ from OrcaSlicer." @@ -7804,7 +7845,7 @@ msgstr "Si prega di correggerli nella scheda dei parametri" msgid "" "The 3MF has the following modified G-code in filament or printer presets:" msgstr "" -"Il 3MF ha i seguenti G-code modificati nei profili del filamento o della " +"Il 3MF ha i seguenti G-code modificati nei profili di filamento o della " "stampante:" msgid "" @@ -7883,7 +7924,7 @@ msgid "Object with multiple parts was detected" msgstr "È stato rilevato un oggetto con più parti" msgid "Auto-Drop" -msgstr "" +msgstr "Rilascio automatico" #, c-format, boost-format msgid "" @@ -7958,9 +7999,11 @@ msgstr "L'oggetto selezionato non può essere diviso." msgid "Disable Auto-Drop to preserve z positioning?\n" msgstr "" +"Disabilitare la funzione di Rilascio automatico per preservare il " +"posizionamento sull'asse Z?\n" msgid "Object with floating parts was detected" -msgstr "" +msgstr "È stato rilevato un oggetto con parti fluttuanti" msgid "Another export job is running." msgstr "È in esecuzione un altro processo di esportazione." @@ -8380,7 +8423,7 @@ msgid "Triangles: %1%\n" msgstr "Triangoli: %1%\n" msgid "Use \"Fix Model\" to repair the mesh." -msgstr "" +msgstr "Utilizza \"Correggi modello\" per riparare la maglia poligonale." #, c-format, boost-format msgid "" @@ -8554,15 +8597,17 @@ msgid "Show the splash screen during startup." msgstr "Mostra la schermata iniziale durante l'avvio." msgid "Show shared profiles notification" -msgstr "" +msgstr "Mostra notifica profili condivisi" msgid "" "Show a notification with a link to browse shared profiles when the selected " "printer is changed." msgstr "" +"Quando si cambia la stampante selezionata, viene visualizzata una notifica " +"con un collegamento per esplorare i profili condivisi." msgid "Use window buttons on left side" -msgstr "" +msgstr "Utilizza i pulsanti della finestra sul lato sinistro" msgid "(Requires restart)" msgstr "(Richiede riavvio)" @@ -8638,10 +8683,10 @@ msgstr "" "del filamento/processo per ciascuna stampante." msgid "Group user filament presets" -msgstr "Raggruppa profili filamento utente" +msgstr "Raggruppa profili di filamento utente" msgid "Group user filament presets based on selection" -msgstr "Raggruppa profili filamento utente in base alla selezione" +msgstr "Raggruppa i profili di filamento utente in base alla selezione" msgid "All" msgstr "Tutto" @@ -8716,7 +8761,7 @@ msgid "Auto arrange plate after cloning" msgstr "Disposizione automatica del piatto dopo la clonazione" msgid "Auto slice after changes" -msgstr "Slicing automatico dopo le modifiche" +msgstr "Elaborazione automatica dopo le modifiche" msgid "" "If enabled, OrcaSlicer will re-slice automatically whenever slicing-related " @@ -8835,6 +8880,10 @@ msgid "" "the transmission of data to Bambu's cloud services too. Users who don't use " "BBL machines or use LAN mode only can safely turn on this function." msgstr "" +"Questa opzione disabilita tutti i servizi cloud, ad esempio Orca Cloud e " +"Bambu Cloud. Interrompe anche la trasmissione dei dati ai servizi cloud di " +"Bambu. Gli utenti che non utilizzano macchine BBL o che utilizzano solo la " +"modalità LAN, possono attivare questa opzione in tutta sicurezza." msgid "Network test" msgstr "Test di rete" @@ -8843,15 +8892,18 @@ msgid "Test" msgstr "Prova" msgid "Cloud Providers" -msgstr "" +msgstr "Fornitori di servizi cloud" msgid "Enable Bambu Cloud" -msgstr "" +msgstr "Abilita Bambu Cloud" msgid "" "Allow logging into Bambu Cloud alongside Orca Cloud. When enabled, a Bambu " "login section appears on the homepage." msgstr "" +"Consente l'accesso sia a Bambu Cloud che a Orca Cloud. Quando questa opzione " +"è abilitata, nella pagina iniziale viene visualizzata una sezione per " +"l'accesso a Bambu." msgid "Update & sync" msgstr "Aggiorna e sincronizza" @@ -9133,7 +9185,7 @@ msgid "Project-inside presets" msgstr "Profilo interno al progetto" msgid "Bundle presets" -msgstr "" +msgstr "Pacchetto profili" msgid "System" msgstr "Sistema" @@ -9738,6 +9790,9 @@ msgid "" "type in the slicing file. Please make sure you have installed the correct " "filament in the external spool." msgstr "" +"Il tipo di filamento esterno è sconosciuto o non corrisponde al tipo di " +"filamento specificato nel file di sezionamento. Assicurati di aver " +"installato il filamento corretto nella bobina esterna." msgid "Please refer to Wiki before use->" msgstr "Fare riferimento alla Wiki prima dell'uso ->" @@ -10129,8 +10184,8 @@ msgstr "" msgid "" "When recording timelapse without toolhead, it is recommended to add a " "\"Timelapse Wipe Tower\" \n" -"by right-click the empty position of build plate and choose \"Add Primitive" -"\"->\"Timelapse Wipe Tower\"." +"by right-click the empty position of build plate and choose \"Add " +"Primitive\"->\"Timelapse Wipe Tower\"." msgstr "" "Quando si registra un timelapse senza la testina di stampa, si consiglia di " "aggiungere un \"Timelapse Torre di spurgo\"\n" @@ -10500,7 +10555,7 @@ msgid "Cooling Fan" msgstr "Velocità minima ventola di raffreddamento" msgid "Fan speed-up time" -msgstr "Tempo di accelerazione della ventola" +msgstr "Tempo accelerazione ventola" msgid "Extruder Clearance" msgstr "Spazio libero estrusore" @@ -10554,10 +10609,10 @@ msgid "Normal" msgstr "Normale" msgid "Resonance Compensation" -msgstr "" +msgstr "Compensazione risonanza" msgid "Resonance Avoidance Speed" -msgstr "Velocità evita risonanza" +msgstr "Velocità prevenzione risonanza" msgid "Frequency" msgstr "Frequenza" @@ -10566,12 +10621,15 @@ msgid "" "The frequency of the anti-vibration signal will correspond to the natural " "frequency of the frame." msgstr "" +"La frequenza del segnale antivibrazione corrisponderà alla frequenza " +"naturale del telaio." msgid "Damping" -msgstr "" +msgstr "Smorzamento" msgid "Damping ratio for the input shaping filter." msgstr "" +"Rapporto di smorzamento per il filtro della compensazione della risonanza." msgid "Speed limitation" msgstr "Limitazione velocità" @@ -10696,29 +10754,42 @@ msgid "" " %s first layer %d %s, other layers %d %s\n" " %s max delta %d %s, current delta %d %s\n" msgstr "" +" - %s:\n" +" %s primo strato %d %s, altri strati %d %s\n" +" %s delta massimo %d %s, delta attuale %d %s\n" msgid "" "Some first-layer and other-layer temperature pairs exceed safety limits.\n" msgstr "" +"Alcune coppie di temperature del primo strato e di altri strati superano i " +"limiti di sicurezza.\n" msgid "" "\n" "Invalid pairs:\n" msgstr "" +"\n" +"Coppie non valide:\n" msgid "" "\n" "You can go back to edit values, or continue if this is intentional." msgstr "" +"\n" +"È possibile tornare indietro per modificare i valori, oppure continuare se " +"questa scelta è intenzionale." msgid "" "\n" "\n" "Continue anyway?" msgstr "" +"\n" +"\n" +"Continuare comunque?" msgid "Temperature Safety Check" -msgstr "" +msgstr "Controllo di sicurezza della temperatura" msgid "Continue" msgstr "Continua" @@ -10727,7 +10798,7 @@ msgid "Back" msgstr "Posteriore" msgid "Don't warn again for this preset" -msgstr "" +msgstr "Non inviare più avvisi per questo profilo" #, c-format, boost-format msgid "Left: %s" @@ -11046,7 +11117,7 @@ msgid "" "presets.\n" "Are you sure you want to continue?" msgstr "" -"La sincronizzazione dei filamenti AMS scarterà i profili filamento " +"La sincronizzazione dei filamenti AMS scarterà i profili di filamento " "modificati ma non salvati.\n" "Sei sicuro di voler continuare?" @@ -11148,7 +11219,7 @@ msgid "" "replaced with the mapped filament types and colors. This action cannot be " "undone." msgstr "" -"Dopo la sincronizzazione, i profili filamento e i colori del progetto " +"Dopo la sincronizzazione, i profili di filamento e i colori del progetto " "saranno sostituiti con i tipi e i colori dei filamenti mappati. Questa " "azione non può essere annullata." @@ -11241,15 +11312,15 @@ msgid "" "This is an expert-level setting, incorrect adjustment will likely lead to " "jams, extruder wheel grinding into filament etc." msgstr "" -"Per modellazione del filamento (o Ramming) si intende l'operazione di " -"estrusione effettuata appena prima di un cambio di filamento in una " -"stampante MM con estrusore singolo. Il suo scopo è quello di dare una forma " -"corretta all'estremità del filamento scaricato, in modo che non impedisca " -"l'inserimento del nuovo filamento e possa essere reinserito successivamente. " -"Questa fase è importante e i diversi materiali possono richiedere velocità " -"di estrusione diverse per ottenere una buona forma. Per questo motivo, le " -"velocità di estrusione nella fase di modellazione del filamento sono " -"regolabili.\n" +"Per Spinta del filamento (o Ramming) si intende l'operazione di estrusione " +"effettuata appena prima di un cambio di filamento in una stampante " +"multimateriale con estrusore singolo. Il suo scopo è quello di dare una " +"forma corretta all'estremità del filamento scaricato, in modo che non " +"impedisca l'inserimento del nuovo filamento e possa essere reinserito " +"successivamente. Questa fase è importante e i diversi materiali possono " +"richiedere velocità di estrusione diverse per ottenere una buona forma. Per " +"questo motivo, le velocità di estrusione nella fase di modellazione del " +"filamento sono regolabili.\n" "\n" "Si tratta di un'impostazione per esperti: una regolazione errata potrebbe " "causare inceppamenti, la macinazione del filamento da parte dell'ingranaggio " @@ -11314,11 +11385,16 @@ msgid "" "Native Wayland liveview requires the GStreamer GTK video sink. Please " "install the gtksink plugin for GStreamer, then restart OrcaSlicer." msgstr "" +"La funzione di visualizzazione in tempo reale nativa di Wayland richiede il " +"ricevitore video GTK di GStreamer. Installare il modulo gtksink per " +"GStreamer e riavviare OrcaSlicer." msgid "" "Failed to initialize the native Wayland GStreamer video sink. Please check " "your GStreamer GTK plugin installation." msgstr "" +"Impossibile inizializzare il ricevitore video nativo di Wayland GStreamer. " +"Verificare l'installazione del modulo GTK di GStreamer." msgid "" "Windows Media Player is required for this task! Do you want to enable " @@ -11361,6 +11437,8 @@ msgstr "" msgid "Cloud agent is not available. Please restart OrcaSlicer and try again." msgstr "" +"Il fornitore di servizi cloud non è disponibile. Riavviare OrcaSlicer e " +"riprovare." msgid "Bambu Network plug-in not detected." msgstr "Modulo di rete Bambu non rilevato." @@ -11541,7 +11619,7 @@ msgid "Zoom out" msgstr "Rimpicciolisci" msgid "Toggle printable for object/part" -msgstr "" +msgstr "Attiva/disattiva stampa per oggetto/parte" msgid "Switch between Prepare/Preview" msgstr "Passa tra Prepara e Anteprima" @@ -11635,7 +11713,7 @@ msgid "New version of Orca Slicer" msgstr "Nuova versione di OrcaSlicer" msgid "Check on Github" -msgstr "" +msgstr "Visualizza su GitHub" msgid "Skip this Version" msgstr "Salta questa versione" @@ -11787,8 +11865,8 @@ msgid "" msgstr "" "È stato rilevato un aggiornamento importante che deve essere eseguito prima " "che la stampa possa continuare. Si desidera aggiornare ora? È possibile " -"effettuare l'aggiornamento anche in un secondo momento da \"Aggiorna firmware" -"\"." +"effettuare l'aggiornamento anche in un secondo momento da \"Aggiorna " +"firmware\"." msgid "" "The firmware version is abnormal. Repairing and updating are required before " @@ -11805,13 +11883,13 @@ msgstr "Scheda di estensione" #, boost-format msgid "Split into %1% parts" -msgstr "" +msgstr "Dividere in %1% parti" msgid "Repair finished" msgstr "Riparazione completata" msgid "Repair failed" -msgstr "" +msgstr "Riparazione fallita" msgid "Repair canceled" msgstr "Riparazione annullata" @@ -11858,10 +11936,10 @@ msgstr "" "l'oggetto potrebbe avere una maglia poligonale difettosa" msgid "Process change extrusion role G-code" -msgstr "" +msgstr "G-code cambio ruolo estrusione processo" msgid "Filament change extrusion role G-code" -msgstr "" +msgstr "G-code cambio ruolo estrusione filamento" msgid "No object can be printed. Maybe too small" msgstr "Non è possibile stampare alcun oggetto. Potrebbe essere troppo piccolo" @@ -11895,15 +11973,19 @@ msgstr "" "La matrice dei volumi di spurgo non corrisponde alla dimensione corretta!" msgid "set_accel_and_jerk() is only supported by Klipper" -msgstr "" +msgstr "set_accel_and_jerk() è supportato solo da Klipper" msgid "" "Input shaping is not supported by Marlin < 2.1.2.\n" "Check your firmware version and update your G-code flavor to ´Marlin 2´" msgstr "" +"La compensazione della risonanza non è supportata da Marlin < 2.1.2.\n" +"Verifica la versione del firmware e aggiorna il G-code a ´Marlin 2´" msgid "Input shaping is only supported by Klipper, RepRapFirmware and Marlin 2" msgstr "" +"La compensazione della risonanza è supportata solo da Klipper, " +"RepRapFirmware e Marlin 2" msgid "Grouping error: " msgstr "Errore di raggruppamento: " @@ -12071,16 +12153,24 @@ msgid "" "temperature must fall within the recommended nozzle temperature range of the " "other filaments. Otherwise, nozzle clogging or printer damage may occur." msgstr "" +"Le temperature degli ugelli selezionate sono incompatibili. La temperatura " +"dell'ugello per ciascun filamento deve rientrare nell'intervallo di " +"temperatura consigliato per gli altri filamenti. In caso contrario, " +"potrebbero verificarsi ostruzioni degli ugelli o danni alla stampante." msgid "" "Invalid recommended nozzle temperature range. The lower bound must be lower " "than the upper bound." msgstr "" +"Intervallo di temperatura dell'ugello consigliato non valido. Il limite " +"inferiore deve essere minore del limite superiore." msgid "" "If you still want to print, you can enable the option in Preferences / " "Control / Slicing / Remove mixed temperature restriction." msgstr "" +"Se desideri comunque stampare, puoi abilitare l'opzione in Preferenze / " +"Controllo / Elaborazione / Rimuovi restrizione temperature miste." msgid "No extrusions under current settings." msgstr "Nessuna estrusione con le impostazioni attuali." @@ -12321,7 +12411,7 @@ msgid "" "each layer to prevent loss of floating point accuracy. Add \"G92 E0\" to " "layer_gcode." msgstr "" -"L'indirizzamento relativo dell'estrusore richiede la reimpostazione della " +"L'indirizzamento relativo dell'estrusore richiede il ripristino della " "posizione dell'estrusore ad ogni strato per evitare la perdita di precisione " "in virgola mobile. Aggiungi \"G92 E0\" a layer_gcode." @@ -12475,7 +12565,7 @@ msgstr "" "compensare l'effetto zampa d'elefante." msgid "Elephant foot compensation layers" -msgstr "Strato di compensazione zampa d'elefante" +msgstr "Strati compensazione zampa d'elefante" msgid "" "The number of layers on which the elephant foot compensation will be active. " @@ -12483,13 +12573,13 @@ msgid "" "the next layers will be linearly shrunk less, up to the layer indicated by " "this value." msgstr "" -"Il numero di strati su cui sarà attiva la compensazione della zampa " -"d'elefante. Il primo strato verrà ridotto in base al valore della " -"compensazione della zampa d'elefante, mentre gli strati successivi saranno " -"ridotti in modo lineare, fino allo strato indicato da questo parametro." +"Numero di strati su cui sarà attiva la compensazione della zampa d'elefante. " +"Il primo strato verrà ridotto in base al valore della compensazione della " +"zampa d'elefante, mentre gli strati successivi saranno ridotti in modo " +"lineare, fino allo strato indicato da questo parametro." msgid "Elephant foot layers density" -msgstr "" +msgstr "Densità strati zampa d'elefante" msgid "" "Density of internal solid infill for Elephant foot layers compensation.\n" @@ -12497,6 +12587,11 @@ msgid "" "Subsequent layers become linearly denser by the height specified in " "elefant_foot_compensation_layers." msgstr "" +"Densità del riempimento solido interno per gli strati di compensazione " +"dell'effetto zampa d'elefante.\n" +"Il valore iniziale è impostato per il secondo strato.\n" +"Gli strati successivi aumentano di densità in modo lineare in base " +"all'altezza specificata in elefant_foot_compensation_layers." msgid "" "Slicing height for each layer. Smaller layer height means more accurate and " @@ -13043,7 +13138,7 @@ msgstr "" "Diminuisci leggermente questo valore (ad esempio 0,9) per ridurre la " "quantità di filamento estruso per i ponti e tendere il materiale.\n" "\n" -"Il flusso effettivo utilizzato nei ponti viene calcolato moltiplicando " +"Il flusso di stampa effettivo per i ponti viene calcolato moltiplicando " "questo valore con il flusso di stampa del filamento e, se impostato, con il " "flusso di stampa dell'oggetto." @@ -13062,9 +13157,10 @@ msgstr "" "Questo valore regola lo spessore dello strato dei ponti interni. È il primo " "strato sopra il riempimento sparso. Ridurre leggermente questo valore (ad " "esempio 0,9) per migliorare la qualità della superficie sopra i riempimenti " -"radi.\n" -"Il flusso effettivo utilizzato nei ponti interni viene calcolato " -"moltiplicando questo valore con il flusso di stampa dei ponti, flusso di " +"sparsi.\n" +"\n" +"Il flusso di stampa effettivo utilizzato per i ponti interni viene calcolato " +"moltiplicando questo valore con il flusso di stampa dei ponti, il flusso di " "stampa del filamento e, se impostato, con il flusso di stampa dell'oggetto." msgid "Top surface flow ratio" @@ -13077,13 +13173,13 @@ msgid "" "The actual top surface flow used is calculated by multiplying this value " "with the filament flow ratio, and if set, the object's flow ratio." msgstr "" -"Questo valore influenza la quantità di materiale utilizzata per il " +"Questo fattore influenza la quantità di materiale utilizzata per il " "riempimento solido della superficie superiore. Puoi diminuirlo leggermente " "per avere una finitura liscia sulla superficie.\n" "\n" -"Il flusso di stampa effettivo utilizzato nelle superfici superiori viene " -"calcolato moltiplicando questo valore con il flusso di stampa del filamento " -"e, se impostato, con il flusso di stampa dell'oggetto." +"Il flusso di stampa effettivo per le superfici superiori viene calcolato " +"moltiplicando questo valore con il flusso di stampa del filamento e, se " +"impostato, con il flusso di stampa dell'oggetto." msgid "Bottom surface flow ratio" msgstr "Flusso di stampa superficie inferiore" @@ -13094,12 +13190,12 @@ msgid "" "The actual bottom solid infill flow used is calculated by multiplying this " "value with the filament flow ratio, and if set, the object's flow ratio." msgstr "" -"Questo valore influenza la quantità di materiale utilizzata per il " +"Questo fattore influenza la quantità di materiale utilizzata per il " "riempimento solido della superficie inferiore.\n" "\n" -"Il flusso di stampa effettivo utilizzato nelle superfici inferiori viene " -"calcolato moltiplicando questo valore con il flusso di stampa del filamento " -"e, se impostato, con il flusso di stampa dell'oggetto." +"Il flusso di stampa effettivo per le superfici inferiori viene calcolato " +"moltiplicando questo valore con il flusso di stampa del filamento e, se " +"impostato, con il flusso di stampa dell'oggetto." msgid "Set other flow ratios" msgstr "Imposta altri rapporti di flusso" @@ -13109,7 +13205,7 @@ msgstr "" "Modifica i rapporti di flusso per altri tipi di percorso di estrusione." msgid "First layer flow ratio" -msgstr "Rapporto di flusso primo strato" +msgstr "Flusso di stampa primo strato" msgid "" "This factor affects the amount of material on the first layer for the " @@ -13118,14 +13214,15 @@ msgid "" "For the first layer, the actual flow ratio for each path role (does not " "affect brims and skirts) will be multiplied by this value." msgstr "" -"Questo fattore influisce sulla quantità di materiale sul primo strato per i " -"ruoli del percorso di estrusione elencati in questa sezione.\n" +"Questo fattore influisce sulla quantità di materiale utilizzata sul primo " +"strato per i ruoli del percorso di estrusione elencati in questa sezione.\n" "\n" -"Per il primo strato, il rapporto di flusso effettivo per ogni ruolo del " -"percorso (non influisce su tese e gonne) sarà moltiplicato per questo valore." +"Per il primo strato, il flusso di stampa effettivo per ogni ruolo del " +"percorso di estrusione (non influisce su tese e gonne) sarà moltiplicato per " +"questo valore." msgid "Outer wall flow ratio" -msgstr "Rapporto di flusso parete esterna" +msgstr "Flusso di stampa pareti esterne" msgid "" "This factor affects the amount of material for outer walls.\n" @@ -13133,14 +13230,15 @@ msgid "" "The actual outer wall flow used is calculated by multiplying this value by " "the filament flow ratio, and if set, the object's flow ratio." msgstr "" -"Questo fattore influisce sulla quantità di materiale per le pareti esterne.\n" +"Questo fattore influisce sulla quantità di materiale utilizzata per le " +"pareti esterne.\n" "\n" -"Il flusso effettivo della parete esterna viene calcolato moltiplicando " -"questo valore per il rapporto di flusso del filamento e, se impostato, per " -"il rapporto di flusso dell'oggetto." +"Il flusso di stampa effettivo per le pareti esterne viene calcolato " +"moltiplicando questo valore per il flusso di stampa del filamento e, se " +"impostato, per il flusso di stampa dell'oggetto." msgid "Inner wall flow ratio" -msgstr "Rapporto di flusso parete interna" +msgstr "Flusso di stampa pareti interne" msgid "" "This factor affects the amount of material for inner walls.\n" @@ -13148,14 +13246,15 @@ msgid "" "The actual inner wall flow used is calculated by multiplying this value by " "the filament flow ratio, and if set, the object's flow ratio." msgstr "" -"Questo fattore influisce sulla quantità di materiale per le pareti interne.\n" +"Questo fattore influisce sulla quantità di materiale utilizzata per le " +"pareti interne.\n" "\n" -"Il flusso effettivo della parete interna viene calcolato moltiplicando " -"questo valore per il rapporto di flusso del filamento e, se impostato, per " -"il rapporto di flusso dell'oggetto." +"Il flusso di stampa effettivo per le pareti interne viene calcolato " +"moltiplicando questo valore per il flusso di stampa del filamento e, se " +"impostato, per il flusso di stampa dell'oggetto." msgid "Overhang flow ratio" -msgstr "Rapporto di flusso per sbalzi" +msgstr "Flusso di stampa sporgenze" msgid "" "This factor affects the amount of material for overhangs.\n" @@ -13163,14 +13262,15 @@ msgid "" "The actual overhang flow used is calculated by multiplying this value by the " "filament flow ratio, and if set, the object's flow ratio." msgstr "" -"Questo fattore influisce sulla quantità di materiale per gli sbalzi.\n" +"Questo fattore influisce sulla quantità di materiale utilizzata per le " +"sporgenze.\n" "\n" -"Il flusso effettivo degli sbalzi viene calcolato moltiplicando questo valore " -"per il rapporto di flusso del filamento e, se impostato, per il rapporto di " -"flusso dell'oggetto." +"Il flusso di stampa effettivo per le sporgenze viene calcolato moltiplicando " +"questo valore per il flusso di stampa del filamento e, se impostato, per il " +"flusso di stampa dell'oggetto." msgid "Sparse infill flow ratio" -msgstr "Rapporto di flusso riempimento sparso" +msgstr "Flusso di stampa riempimento sparso" msgid "" "This factor affects the amount of material for sparse infill.\n" @@ -13178,15 +13278,15 @@ msgid "" "The actual sparse infill flow used is calculated by multiplying this value " "by the filament flow ratio, and if set, the object's flow ratio." msgstr "" -"Questo fattore influisce sulla quantità di materiale per il riempimento " -"sparso.\n" +"Questo fattore influisce sulla quantità di materiale utilizzata per il " +"riempimento sparso.\n" "\n" -"Il flusso effettivo del riempimento sparso viene calcolato moltiplicando " -"questo valore per il rapporto di flusso del filamento e, se impostato, per " -"il rapporto di flusso dell'oggetto." +"Il flusso di stampa effettivo per il riempimento sparso viene calcolato " +"moltiplicando questo valore per il flusso di stampa del filamento e, se " +"impostato, per il flusso di stampa dell'oggetto." msgid "Internal solid infill flow ratio" -msgstr "Rapporto di flusso riempimento solido interno" +msgstr "Flusso di stampa riempimento solido interno" msgid "" "This factor affects the amount of material for internal solid infill.\n" @@ -13194,15 +13294,15 @@ msgid "" "The actual internal solid infill flow used is calculated by multiplying this " "value by the filament flow ratio, and if set, the object's flow ratio." msgstr "" -"Questo fattore influisce sulla quantità di materiale per il riempimento " -"solido interno.\n" +"Questo fattore influisce sulla quantità di materiale utilizzata per il " +"riempimento solido interno.\n" "\n" -"Il flusso effettivo del riempimento solido interno viene calcolato " -"moltiplicando questo valore per il rapporto di flusso del filamento e, se " -"impostato, per il rapporto di flusso dell'oggetto." +"Il flusso di stampa effettivo per il riempimento solido interno viene " +"calcolato moltiplicando questo valore per il flusso di stampa del filamento " +"e, se impostato, per il flusso di stampa dell'oggetto." msgid "Gap fill flow ratio" -msgstr "Rapporto di flusso riempimento spazi" +msgstr "Flusso di stampa riempimento spazi" msgid "" "This factor affects the amount of material for filling the gaps.\n" @@ -13210,15 +13310,15 @@ msgid "" "The actual gap filling flow used is calculated by multiplying this value by " "the filament flow ratio, and if set, the object's flow ratio." msgstr "" -"Questo fattore influisce sulla quantità di materiale per il riempimento " -"degli spazi.\n" +"Questo fattore influisce sulla quantità di materiale utilizzata per il " +"riempimento degli spazi vuoti.\n" "\n" -"Il flusso effettivo del riempimento spazi viene calcolato moltiplicando " -"questo valore per il rapporto di flusso del filamento e, se impostato, per " -"il rapporto di flusso dell'oggetto." +"Il flusso di stampa effettivo per il riempimento degli spazi vuoti viene " +"calcolato moltiplicando questo valore per il flusso di stampa del filamento " +"e, se impostato, per il flusso di stampa dell'oggetto." msgid "Support flow ratio" -msgstr "Rapporto di flusso supporto" +msgstr "Flusso di stampa supporti" msgid "" "This factor affects the amount of material for support.\n" @@ -13226,14 +13326,15 @@ msgid "" "The actual support flow used is calculated by multiplying this value by the " "filament flow ratio, and if set, the object's flow ratio." msgstr "" -"Questo fattore influisce sulla quantità di materiale per il supporto.\n" +"Questo fattore influisce sulla quantità di materiale utilizzata per i " +"supporti.\n" "\n" -"Il flusso effettivo del supporto viene calcolato moltiplicando questo valore " -"per il rapporto di flusso del filamento e, se impostato, per il rapporto di " -"flusso dell'oggetto." +"Il flusso di stampa effettivo per i supporti viene calcolato moltiplicando " +"questo valore per il flusso di stampa del filamento e, se impostato, per il " +"flusso di stampa dell'oggetto." msgid "Support interface flow ratio" -msgstr "Rapporto di flusso interfaccia supporto" +msgstr "Flusso di stampa interfaccie supporti" msgid "" "This factor affects the amount of material for the support interface.\n" @@ -13241,12 +13342,12 @@ msgid "" "The actual support interface flow used is calculated by multiplying this " "value by the filament flow ratio, and if set, the object's flow ratio." msgstr "" -"Questo fattore influisce sulla quantità di materiale per l'interfaccia di " -"supporto.\n" +"Questo fattore influisce sulla quantità di materiale utilizzata per le " +"interfacce dei supporti.\n" "\n" -"Il flusso effettivo dell'interfaccia di supporto viene calcolato " -"moltiplicando questo valore per il rapporto di flusso del filamento e, se " -"impostato, per il rapporto di flusso dell'oggetto." +"Il flusso di stampa effettivo per le interfacce dei supporti viene calcolato " +"moltiplicando questo valore per il flusso di stampa del filamento e, se " +"impostato, per il flusso di stampa dell'oggetto." msgid "Precise wall" msgstr "Parete precisa" @@ -13391,7 +13492,7 @@ msgid "Sacrificial layer" msgstr "Strato sacrificale" msgid "Reverse threshold" -msgstr "Soglia inversa" +msgstr "Soglia di inversione" msgid "Overhang reversal threshold" msgstr "Soglia di inversione sporgenze" @@ -13518,7 +13619,7 @@ msgstr "" "più facile la rimozione della tesa." msgid "Brim flow ratio" -msgstr "" +msgstr "Flusso di stampa tese" msgid "" "This factor affects the amount of material for brims.\n" @@ -13528,9 +13629,17 @@ msgid "" "\n" "Note: The resulting value will not be affected by the first-layer flow ratio." msgstr "" +"Questo valore influisce sulla quantità di materiale utilizzata per le tese.\n" +"\n" +"Il flusso di stampa effettivo per le tese viene calcolato moltiplicando " +"questo valore per il flusso di stampa del filamento e, se impostato, per il " +"flusso di stampa dell'oggetto.\n" +"\n" +"Nota: il valore risultante non sarà influenzato dal flusso di stampa del " +"primo strato." msgid "Brim follows compensated outline" -msgstr "Tesa segue il contorno compensato" +msgstr "Tesa su contorno con compensazione" msgid "" "When enabled, the brim is aligned with the first-layer perimeter geometry " @@ -13541,21 +13650,24 @@ msgid "" "If your current setup already works well, enabling it may be unnecessary and " "can cause the brim to fuse with upper layers." msgstr "" -"Quando abilitato, tesa è allineato con la geometria perimetrale del primo " -"strato dopo l'applicazione della compensazione del piede di elefante.\n" -"Questa opzione è prevista per i casi in cui è prevista la compensazione del " -"piede di elefante altera significativamente l'impronta del primo strato.\n" +"Se abilitata, la tesa viene allineata con il perimetro del primo strato dopo " +"l'applicazione della Compensazione della zampa d'elefante.\n" +"Questa opzione è pensata per i casi in cui la compensazione della zampa di " +"elefante altera significativamente le dimensioni del primo strato.\n" "\n" -"Se la tua configurazione attuale funziona già bene, abilitarla potrebbe non " -"essere necessaria e può causare la fusione del tesa con gli strati superiori." +"Se la tua configurazione attuale funziona in modo ottimale, non è necessario " +"abilitare questa opzione, in quanto potrebbe causare la fusione della tesa " +"con gli strati superiori." msgid "Combine brims" -msgstr "" +msgstr "Unisci tese" msgid "" "Combine multiple brims into one when they are close to each other. This can " "improve brim adhesion." msgstr "" +"Unisce più tese in una quando sono vicine tra loro. Questo può migliorare " +"l'adesione delle tese." msgid "Brim ears" msgstr "Tesa ad orecchio" @@ -13688,14 +13800,18 @@ msgstr "" msgid "" "Enable this to override the fan speed set in custom G-code during print." msgstr "" +"Abilita questa opzione per sovrascrivere la velocità della ventola impostata " +"nel G-code personalizzato durante la stampa." msgid "On completion" -msgstr "" +msgstr "Al completamento" msgid "" "Enable this to override the fan speed set in custom G-code after print " "completion." msgstr "" +"Abilita questa opzione per sovrascrivere la velocità della ventola impostata " +"nel G-code personalizzato una volta terminata la stampa." msgid "" "Speed of exhaust fan during printing. This speed will override the speed in " @@ -14130,6 +14246,13 @@ msgid "" "\n" "This option will be disabled if spiral vase mode is enabled." msgstr "" +"Direzione in cui vengono estrusi i perimetri di stampa, guardando dall'alto " +"verso il basso.\n" +"I fori vengono stampati nella direzione opposta ai perimetri, per mantenere " +"l'allineamento con gli strati i cui contorni sono incompleti e cambiano " +"direzione, formando parzialmente contorni di fori.\n" +"\n" +"Questa opzione verrà disabilitata se la modalità vaso a spirale è attiva." msgid "Counter clockwise" msgstr "Senso antiorario" @@ -14777,9 +14900,9 @@ msgid "" "this movement should be before the filament is retracted again." msgstr "" "Se impostato su un valore diverso da zero, il filamento viene spostato verso " -"l'ugello tra i singoli movimenti nei tubi di raffreddamento (\"timbratura" -"\"). Questa opzione configura la durata di questo movimento prima che il " -"filamento venga nuovamente retratto." +"l'ugello tra i singoli movimenti nei tubi di raffreddamento " +"(\"timbratura\"). Questa opzione configura la durata di questo movimento " +"prima che il filamento venga nuovamente retratto." msgid "Speed of the first cooling move" msgstr "Velocità del primo movimento di raffreddamento" @@ -14807,10 +14930,11 @@ msgstr "" "oggetti sacrificali o riempimenti." msgid "Wipe tower cooling" -msgstr "" +msgstr "Raffreddamento torre di spurgo" msgid "Temperature drop before entering filament tower" msgstr "" +"Calo di temperatura prima dell'ingresso nella torre di spurgo del filamento" msgid "Interface layer pre-extrusion distance" msgstr "Distanza di pre-estrusione dello strato di interfaccia" @@ -14880,7 +15004,7 @@ msgid "" "This string is edited by RammingDialog and contains ramming specific " "parameters." msgstr "" -"Questa stringa è stata modifcata da RammingDialog e contiene parametri " +"Questa stringa è stata modificata da RammingDialog e contiene parametri " "specifici per l'operazione di modellazione della punta del filamento." msgid "Enable ramming for multi-tool setups" @@ -15264,12 +15388,14 @@ msgstr "" "inferiore, è possibile migliorare l'adesione sul piano di stampa." msgid "First layer travel" -msgstr "" +msgstr "Spostamento primo strato" msgid "" "Travel acceleration of first layer.\n" "The percentage value is relative to Travel Acceleration." msgstr "" +"Accelerazione dello spostamento nel primo strato.\n" +"Il valore percentuale è relativo all'accelerazione dello spostamento." msgid "Enable accel_to_decel" msgstr "Abilita accel_to_decel" @@ -15323,6 +15449,8 @@ msgid "" "Travel jerk of first layer.\n" "The percentage value is relative to Travel Jerk." msgstr "" +"Scatto dello spostamento nel primo strato.\n" +"Il valore percentuale è relativo allo scatto dello spostamento." msgid "" "Line width of the first layer. If expressed as a %, it will be computed over " @@ -15379,21 +15507,21 @@ msgstr "" "Temperatura dell'ugello per la stampa del primo strato con questo filamento." msgid "Full fan speed at layer" -msgstr "Velocità massima della ventola su strato" +msgstr "Velocità massima ventola su strato" msgid "" "Fan speed will be ramped up linearly from zero at layer " -"\"close_fan_the_first_x_layers\" to maximum at layer \"full_fan_speed_layer" -"\". \"full_fan_speed_layer\" will be ignored if lower than " -"\"close_fan_the_first_x_layers\", in which case the fan will be running at " -"maximum allowed speed at layer \"close_fan_the_first_x_layers\" + 1." +"\"close_fan_the_first_x_layers\" to maximum at layer " +"\"full_fan_speed_layer\". \"full_fan_speed_layer\" will be ignored if lower " +"than \"close_fan_the_first_x_layers\", in which case the fan will be running " +"at maximum allowed speed at layer \"close_fan_the_first_x_layers\" + 1." msgstr "" "La velocità della ventola aumenterà in modo lineare da zero nello strato " "\"close_fan_the_first_x_layers\" al massimo nello strato " "\"full_fan_speed_layer\". Se inferiore a \"close_fan_the_first_x_layers\", " "\"full_fan_speed_layer\" verrà ignorato. in tal caso la ventola funzionerà " -"alla massima velocità consentita nello strato \"close_fan_the_first_x_layers" -"\" + 1." +"alla massima velocità consentita nello strato " +"\"close_fan_the_first_x_layers\" + 1." msgid "layer" msgstr "strato" @@ -15617,6 +15745,17 @@ msgid "" "Ripple: Uniform ripple pattern that ripples left and right of the original " "path. Repeating pattern, woven appearance." msgstr "" +"Tipo di rumore da utilizzare per la generazione della superficie ruvida:\n" +"Classico: rumore casuale uniforme classico.\n" +"Perlin: rumore Perlin, che conferisce una trama più uniforme.\n" +"Ondulato: Simile al rumore Perlin, ma più granuloso.\n" +"Multifrattale ruvido: rumore con superfici nette e frastagliate. Crea trame " +"simili al marmo.\n" +"Voronoi: divide la superficie in tassellazioni di Voronoi e ne sposta " +"ciascuna di una quantità casuale. Crea una trama a mosaico.\n" +"Increspature: Motivo con onde uniformi che si propagano a sinistra e a " +"destra del percorso originale. Motivo ripetuto, aspetto in stile tessuto " +"intrecciato." msgid "Classic" msgstr "Classico" @@ -15628,13 +15767,13 @@ msgid "Billow" msgstr "Ondulato" msgid "Ridged Multifractal" -msgstr "Multifrattale increspato" +msgstr "Multifrattale ruvido" msgid "Voronoi" msgstr "Voronoi" msgid "Ripple" -msgstr "" +msgstr "Increspature" msgid "Fuzzy skin feature size" msgstr "Dimensione struttura superficie ruvida" @@ -15667,13 +15806,15 @@ msgstr "" "daranno luogo a un rumore più uniforme." msgid "Number of ripples per layer" -msgstr "" +msgstr "Numero increspature per strato" msgid "Controls how many full cycles of ripples will be added per layer." msgstr "" +"Controlla quanti cicli completi di increspature verranno aggiunti per ogni " +"strato." msgid "Ripple offset" -msgstr "" +msgstr "Deviazione increspature" msgid "" "Shifts the ripple phase forward along the print path by the specified " @@ -15687,9 +15828,20 @@ msgid "" "The shift is applied once every number of layers set by Layers between " "ripple offset, so layers within the same group are printed identically." msgstr "" +"Sposta in avanti la fase dell'increspatura, lungo il percorso di stampa, " +"della percentuale specificata di lunghezza d'onda.\n" +"- 0% mantieni ogni strato identico.\n" +"- 50% sposta il motivo di mezza lunghezza d'onda, invertendo di fatto la " +"fase.\n" +"- 100% sposta il motivo di un'intera lunghezza d'onda, tornando alla fase " +"originale.\n" +"\n" +"Lo spostamento viene applicato una volta ogni numero di strati impostato " +"nell'opzione \"Strati deviazione increspature\", in modo che gli strati " +"all'interno dello stesso gruppo vengano stampati in modo identico." msgid "Layers between ripple offset" -msgstr "" +msgstr "Strati deviazione increspature" msgid "" "Specifies how many consecutive layers share the same ripple phase before the " @@ -15702,6 +15854,15 @@ msgid "" "to 6 are shifted by the configured offset, then layers 7 to 9 return to the " "base pattern, etc." msgstr "" +"Specifica quanti strati consecutivi condividono la stessa fase di " +"increspatura prima che venga applicata la deviazione.\n" +"Per esempio:\n" +"- 1 = lo strato 1 viene stampato con il motivo di increspatura di base, lo " +"strato 2 viene spostato in base alla deviazione configurata, lo strato 3 " +"ritorna al motivo di base e così via.\n" +"- 3 = Gli strati da 1 a 3 vengono stampati con il motivo di increspatura di " +"base, gli strati da 4 a 6 vengono spostati in base alla deviazione " +"configurata, gli strati da 7 a 9 ritornano al motivo di base e così via." msgid "Filter out tiny gaps" msgstr "Filtra piccoli spazi vuoti" @@ -15895,7 +16056,7 @@ msgid "Will only take into account the delay for the cooling of overhangs." msgstr "Terrà conto solo del ritardo per il raffreddamento delle sporgenze." msgid "Fan kick-start time" -msgstr "Tempo di avvio della ventola" +msgstr "Tempo avvio ventola" msgid "" "Emit a max fan speed command for this amount of seconds before reducing to " @@ -16370,7 +16531,7 @@ msgid "Print speed of ironing lines." msgstr "Indica la velocità di stampa per le linee di stiratura." msgid "Ironing angle offset" -msgstr "Offset angolo di stiratura" +msgstr "Angolo di stiratura" msgid "The angle of ironing lines offset from the top surface." msgstr "L'angolo delle linee di stiratura rispetto alla superficie superiore." @@ -16382,42 +16543,53 @@ msgid "Use a fixed absolute angle for ironing." msgstr "Usa un angolo assoluto fisso per la stiratura." msgid "Ironing expansion" -msgstr "" +msgstr "Espansione stiratura" msgid "Expand or contract the ironing area." -msgstr "" +msgstr "Espande o riduce l'area di stiratura." msgid "Z contouring enabled" -msgstr "" +msgstr "Contornatura Z abilitata" msgid "Enable Z-layer contouring (aka Z-layer anti-aliasing)." msgstr "" +"Abilita la contornatura degli strati sull'asse Z (noto anche come anti-" +"aliasing dell'asse Z)." msgid "Minimize wall height angle" -msgstr "" +msgstr "Riduci altezza su pareti inclinate" msgid "" "Reduce the height of top-surface perimeters to match the model edge height.\n" "Affects perimeters with a slope less than this angle (degrees).\n" "A reasonable value is 35. Set to 0 to disable." msgstr "" +"Riduce l'altezza dei perimetri della superficie superiore in modo che si " +"adattino meglio al bordo del modello\n" +"Influisce sui perimetri con una pendenza inferiore a questo angolo (in " +"gradi).\n" +"Il valore consigliato è 35. Impostare a 0 per disabilitare questa funzione." msgid "°" msgstr "°" msgid "Don't alternate fill direction" -msgstr "" +msgstr "Non alternare direzione riempimento" msgid "Disable alternating fill direction when using Z contouring." msgstr "" +"Disabilita il riempimento a direzione alternata quando si utilizza l'opzione " +"\"Contornatura Z\"." msgid "Minimum z height" -msgstr "" +msgstr "Altezza Z minima" msgid "" "Minimum Z-layer height.\n" "Also controls the slicing plane." msgstr "" +"Altezza minima dello strato sull'asse Z.\n" +"Controlla anche il piano per l'elaborazione." msgid "This G-code is inserted at every layer change after the Z lift." msgstr "" @@ -16599,16 +16771,17 @@ msgstr "" "Marlin 2." msgid "Resonance avoidance" -msgstr "Evitamento della risonanza" +msgstr "Prevenzione risonanza" msgid "" "By reducing the speed of the outer wall to avoid the resonance zone of the " "printer, ringing on the surface of the model are avoided.\n" "Please turn this option off when testing ringing." msgstr "" -"Riducendo la velocità della parete esterna per evitare la zona di risonanza " -"della stampante, si evitano le ondulazioni sulla superficie del modello.\n" -"Disattivare questa opzione quando si testa il ringing." +"Riducendo la velocità nelle pareti esterne per evitare la zona di risonanza " +"della stampante, si evitano le ondulazioni o ronzii (ringing) sulla " +"superficie del modello.\n" +"Disattivare questa opzione quando si testa il ronzio ." msgid "Min" msgstr "Minimo" @@ -16623,12 +16796,14 @@ msgid "Maximum speed of resonance avoidance." msgstr "Velocità massima per l'evitamento della risonanza." msgid "Emit input shaping" -msgstr "" +msgstr "Sovrascrivi compensazione risonanza" msgid "" "Override firmware input shaping settings.\n" "If disabled, firmware settings are used." msgstr "" +"Sovrascrivi le impostazioni di compensazione della risonanza del firmware.\n" +"Se disabilitato, verranno utilizzate le impostazioni del firmware." msgid "Input shaper type" msgstr "Tipo di compensazione della risonanza" @@ -16638,42 +16813,44 @@ msgid "" "Default uses the firmware default settings.\n" "Disable turns off input shaping in the firmware." msgstr "" +"Seleziona l'algoritmo di compensazione della risonanza.\n" +"L'opzione \"Default\" utilizza le impostazioni predefinite del firmware." msgid "MZV" -msgstr "" +msgstr "MZV" msgid "ZV" -msgstr "" +msgstr "ZV" msgid "ZVD" -msgstr "" +msgstr "ZVD" msgid "ZVDD" -msgstr "" +msgstr "ZVDD" msgid "ZVDDD" -msgstr "" +msgstr "ZVDDD" msgid "EI" -msgstr "" +msgstr "EI" msgid "EI2" -msgstr "" +msgstr "EI2" msgid "2HUMP_EI" -msgstr "" +msgstr "2HUMP_EI" msgid "EI3" -msgstr "" +msgstr "EI3" msgid "3HUMP_EI" -msgstr "" +msgstr "3HUMP_EI" msgid "DAA" -msgstr "" +msgstr "DAA" msgid "X" -msgstr "" +msgstr "X" msgid "" "Resonant frequency for the X axis input shaper.\n" @@ -16681,15 +16858,26 @@ msgid "" "To disable input shaping, use the Disable type.\n" "RRF: X and Y values are equal." msgstr "" +"Frequenza di risonanza per la compensazione sull'asse X.\n" +"Se impostato a zero, verranno utilizzate le impostazioni di frequenza del " +"firmware.\n" +"Per disabilitare la compensazione della risonanza, selezionare il tipo " +"\"Disabilita\".\n" +"RRF: i valori X e Y sono uguali." msgid "Y" -msgstr "" +msgstr "Y" msgid "" "Resonant frequency for the Y axis input shaper.\n" "Zero will use the firmware frequency.\n" "To disable input shaping, use the Disable type." msgstr "" +"Frequenza di risonanza per la compensazione sull'asse Y.\n" +"Se impostato a zero, verranno utilizzate le impostazioni di frequenza del " +"firmware.\n" +"Per disabilitare la compensazione della risonanza, selezionare il tipo " +"\"Disabilita\"." msgid "" "Damping ratio for the X axis input shaper.\n" @@ -16697,12 +16885,23 @@ msgid "" "To disable input shaping, use the Disable type.\n" "RRF: X and Y values are equal." msgstr "" +"Rapporto di smorzamento per la compensazione della risonanza sull'asse X.\n" +"Se impostato a zero, verranno utilizzate le impostazioni di rapporto di " +"smorzamento del firmware.\n" +"Per disabilitare la compensazione della risonanza, selezionare il tipo " +"\"Disabilita\".\n" +"RRF: i valori X e Y sono uguali." msgid "" "Damping ratio for the Y axis input shaper.\n" "Zero will use the firmware damping ratio.\n" "To disable input shaping, use the Disable type." msgstr "" +"Rapporto di smorzamento per la compensazione della risonanza sull'asse Y.\n" +"Se impostato a zero, verranno utilizzate le impostazioni di rapporto di " +"smorzamento del firmware.\n" +"Per disabilitare la compensazione della risonanza, selezionare il tipo " +"\"Disabilita\"." msgid "" "Part cooling fan speed may be increased when auto cooling is enabled. This " @@ -16840,10 +17039,12 @@ msgstr "" "utilizzare questa funzione. Comando G-code: M106 P2 S(0-255)" msgid "For the first" -msgstr "" +msgstr "Per il primo" msgid "Set special auxiliary cooling fan for the first certain layers." msgstr "" +"Imposta la ventola di raffreddamento ausiliaria per i primi strati " +"specificati." msgid "" "Auxiliary fan speed will be ramped up linearly from layer \"For the first\" " @@ -16852,10 +17053,18 @@ msgid "" "in which case the fan will run at maximum allowed speed at layer \"For the " "first\" + 1." msgstr "" +"La velocità della ventola ausiliaria aumenterà linearmente da \"Per il " +"primo\" fino al valore massimo impostato su \"Velocità massima ventola su " +"strato\".\n" +"Il valore \"Velocità massima ventola su strato\" verrà ignorato se inferiore " +"a \"Per il primo\"; in tal caso, la ventola funzionerà alla velocità massima " +"consentita con il valore \"Per il primo\" + 1." msgid "" "Special auxiliary cooling fan speed, effective only for the first x layers." msgstr "" +"Velocità della ventola di raffreddamento ausiliaria, operativa solo per i " +"primi x strati." msgid "" "The lowest printable layer height for the extruder. Used to limit the " @@ -17094,12 +17303,15 @@ msgstr "" "OrcaSlicer leggendo le variabili di ambiente." msgid "Change extrusion role G-code (process)" -msgstr "" +msgstr "Modifica G-code ruolo di estrusione (processo)" msgid "" "This G-code is inserted when the extrusion role is changed. It runs after " "the machine and filament extrusion role G-code." msgstr "" +"Questo G-code viene inserito quando si effettua il cambio del ruolo di " +"estrusione. Viene eseguito dopo il G-code della macchina e del ruolo di " +"estrusione del filamento." msgid "Printer type" msgstr "Tipo stampante" @@ -17944,7 +18156,7 @@ msgstr "" "l'azione di cambio filamento manuale." msgid "Wipe tower type" -msgstr "Tipo di torre di pulizia" +msgstr "Tipo di torre di spurgo" msgid "" "Choose the wipe tower implementation for multi-material prints. Type 1 is " @@ -18107,10 +18319,11 @@ msgstr "" "sbalzi, ecc." msgid "Ignore small overhangs" -msgstr "Ignora sbalzi piccoli" +msgstr "Ignora piccole sporgenze" msgid "Ignore small overhangs that possibly don't require support." -msgstr "Ignora i piccoli sbalzi che potrebbero non richiedere supporto." +msgstr "" +"Ignora le piccole sporgenze che potrebbero non necessitare dei supporti." msgid "Top Z distance" msgstr "Distanza Z superiore" @@ -18330,15 +18543,18 @@ msgid "Threshold angle" msgstr "Angolo di soglia" msgid "" -"Support will be generated for overhangs whose slope angle is below the threshold. " -"The smaller this value is, the steeper the overhang that can be printed without support.\n" -"Note: If set to 0, normal supports use the Threshold overlap instead, " -"while tree supports fall back to a default value of 30." +"Support will be generated for overhangs whose slope angle is below the " +"threshold. The smaller this value is, the steeper the overhang that can be " +"printed without support.\n" +"Note: If set to 0, normal supports use the Threshold overlap instead, while " +"tree supports fall back to a default value of 30." msgstr "" -"I supporti verranno generati per gli sbalzi il cui angolo di inclinazione è inferiore alla soglia." -"Più piccolo è questo valore, più ripido sarà lo sbalzo che può essere stampato senza supporto.\n" -"Nota: se impostato su 0, i supporti normali useranno invece Soglia sovrapposizione, " -"mentre i supporti ad albero torneranno al valore predefinito di 30." +"I supporti verranno generati per le sporgenze il cui angolo di inclinazione " +"è inferiore a questa soglia. Più piccolo è questo valore, più ripide saranno " +"le sporgenze stampabili senza supporti.\n" +"Nota: se impostato a 0, i supporti normali useranno il valore specificato in " +"\"Soglia sovrapposizione\", mentre i supporti ad albero torneranno al valore " +"predefinito di 30." msgid "Threshold overlap" msgstr "Soglia sovrapposizione" @@ -18350,8 +18566,8 @@ msgid "" msgstr "" "Se l'angolo di soglia è zero, i supporti verranno generati per le sporgenze " "la cui sovrapposizione è al di sotto della soglia. Quanto più piccolo è " -"questo valore, tanto più ripida sarà la sporgenza che può essere stampata " -"senza supporto." +"questo valore, tanto più ripide saranno le sporgenze stampabili senza " +"supporti." msgid "Tree support branch angle" msgstr "Angolo rami supporti ad albero" @@ -18509,8 +18725,8 @@ msgstr "Attiva controllo della temperatura" msgid "" "Enable this option for automated chamber temperature control. This option " -"activates the emitting of an M191 command before the \"machine_start_gcode" -"\"\n" +"activates the emitting of an M191 command before the " +"\"machine_start_gcode\"\n" " which sets the chamber temperature and waits until it is reached. In " "addition, it emits an M141 command at the end of the print to turn off the " "chamber heater, if present.\n" @@ -18598,12 +18814,14 @@ msgstr "" "Questo gcode viene inserito quando viene modificato il ruolo di estrusione." msgid "Change extrusion role G-code (filament)" -msgstr "" +msgstr "Modifica G-code ruolo di estrusione (filamento)" msgid "" "This G-code is inserted when the extrusion role is changed for the active " "filament." msgstr "" +"Questo G-code viene inserito quando si effettua il cambio del ruolo di " +"estrusione per il filamento attivo." msgid "" "Line width for top surfaces. If expressed as a %, it will be computed over " @@ -19217,15 +19435,18 @@ msgstr "" "singola." msgid "Maximum wall resolution" -msgstr "" +msgstr "Risoluzione massima parete" msgid "" "This value determines the smallest wall line segment length in mm. The " "smaller you set this value, the more accurate and precise the walls will be." msgstr "" +"Questo valore determina la lunghezza minima del segmento di linea della " +"parete, in mm. Più piccolo è il valore impostato, più accurate e precise " +"saranno le pareti." msgid "Maximum wall deviation" -msgstr "" +msgstr "Deviazione massima parete" msgid "" "The maximum deviation allowed when reducing the resolution for the 'Maximum " @@ -19234,6 +19455,11 @@ msgid "" "'Maximum wall resolution', so if the two conflict, 'Maximum wall deviation' " "takes precedence." msgstr "" +"Deviazione massima consentita quando si riduce la risoluzione tramite " +"l'impostazione \"Risoluzione massima parete\". Se si aumenta questo valore, " +"la stampa sarà meno precisa, ma il G-Code sarà più contenuto. \"Deviazione " +"massima parete\" limita \"Risoluzione massima parete\", quindi in caso di " +"conflitto tra i due valori, \"Deviazione massima parete\" ha la precedenza." msgid "First layer minimum wall width" msgstr "Larghezza minima parete del primo strato" @@ -20023,7 +20249,7 @@ msgid "Generating infill toolpath" msgstr "Generazione percorso testina per riempimento" msgid "Z contouring" -msgstr "" +msgstr "Contornatura Z" msgid "Detect overhangs for auto-lift" msgstr "Rilevamento sporgenze per il sollevamento automatico" @@ -20660,6 +20886,11 @@ msgid "" "temperature range of the other filaments. Otherwise, nozzle clogging or " "printer damage may occur." msgstr "" +"Le temperature degli ugelli selezionate sono incompatibili. Per la stampa " +"multimateriale, la temperatura dell'ugello per ciascun filamento deve " +"rientrare nell'intervallo di temperatura consigliato per gli altri " +"filamenti. In caso contrario, potrebbero verificarsi ostruzioni degli ugelli " +"o danni alla stampante." msgid "Sync AMS and nozzle information" msgstr "Sincronizza informazioni AMS e ugello" @@ -21090,25 +21321,25 @@ msgid "NOTE: High values may cause Layer shift (>%s)" msgstr "NOTA: valori elevati possono causare spostamento degli strati (>%s)" msgid "Flow Ratio Calibration" -msgstr "" +msgstr "Calibrazione flusso di stampa" msgid "Calibration Test Type" -msgstr "" +msgstr "Tipo test calibrazione" msgid "Pass 1 (Coarse)" -msgstr "" +msgstr "Passaggio 1 (Impreciso)" msgid "Pass 2 (Fine)" -msgstr "" +msgstr "Passaggio 2 (Fine)" msgid "YOLO (Recommended)" msgstr "YOLO (Consigliato)" msgid "YOLO (Perfectionist)" -msgstr "" +msgstr "YOLO (Perfezionista)" msgid "Top Surface Pattern" -msgstr "" +msgstr "Motivo superficie superiore" msgid "Send G-code to printer host" msgstr "Invia G-code all'host di stampa" @@ -21378,12 +21609,12 @@ msgstr "" "Vuoi riscriverlo?" msgid "" -"We would rename the presets as \"Vendor Type Serial @printer you selected" -"\".\n" +"We would rename the presets as \"Vendor Type Serial @printer you " +"selected\".\n" "To add preset for more printers, please go to printer selection" msgstr "" -"Rinomineremo i profili come \"Produttore Tipo Seriale @stampante selezionata" -"\".\n" +"Rinomineremo i profili come \"Produttore Tipo Seriale @stampante " +"selezionata\".\n" "Per aggiungere il profilo per più stampanti, vai alla selezione della " "stampante" @@ -21505,7 +21736,7 @@ msgstr "" "Il nome del profilo della stampante che hai creato esiste già. Vuoi " "sovrascriverlo?\n" "\tSì: Sovrascrivi il profilo della stampante con lo stesso nome, e i profili " -"del filamento e del processo con lo stesso nome saranno ricreati,\n" +"di filamento e di processo con lo stesso nome saranno ricreati,\n" "mentre gli altri saranno conservati.\n" "\tAnnulla: Non creare un profilo, torna alla pagina di creazione." @@ -21626,7 +21857,7 @@ msgid "Printer presets(.zip)" msgstr "Profili stampante (.zip)" msgid "Filament presets(.zip)" -msgstr "Profili filamento (.zip)" +msgstr "Profili di filamento (.zip)" msgid "Process presets(.zip)" msgstr "Profili di processo (.zip)" @@ -22738,7 +22969,7 @@ msgid "Global settings" msgstr "Impostazioni globali" msgid "Video tutorial" -msgstr "" +msgstr "Video tutorial" msgid "(Sync with printer)" msgstr "(Sincronizza con stampante)" @@ -23017,7 +23248,7 @@ msgid "" "Generic filament presets will be used." msgstr "" "Il filamento potrebbe non essere compatibile con le impostazioni correnti " -"della macchina. Verranno utilizzati i profili filamento generici." +"della macchina. Verranno utilizzati i profili di filamento generici." msgid "" "The filament model is unknown. Still using the previous filament preset." @@ -23027,7 +23258,7 @@ msgstr "" msgid "The filament model is unknown. Generic filament presets will be used." msgstr "" -"Il modello di filamento è sconosciuto. Verranno utilizzati i profili " +"Il modello di filamento è sconosciuto. Verranno utilizzati i profili di " "filamento generici." msgid "" @@ -25417,12 +25648,13 @@ msgstr "" #~ "nostro wiki.\n" #~ "\n" #~ "Di solito la calibrazione non è necessaria. Quando si avvia una stampa a " -#~ "singolo colore/materiale, con l'opzione \"calibrazione dinamica del flusso" -#~ "\" selezionata nel menu di avvio della stampa, la stampante seguirà il " -#~ "vecchio modo, calibrando il filamento prima della stampa; Quando si avvia " -#~ "una stampa multicolore/materiale, la stampante utilizzerà il parametro di " -#~ "compensazione predefinito per il filamento durante ogni cambio di " -#~ "filamento, che avrà un buon risultato nella maggior parte dei casi.\n" +#~ "singolo colore/materiale, con l'opzione \"calibrazione dinamica del " +#~ "flusso\" selezionata nel menu di avvio della stampa, la stampante seguirà " +#~ "il vecchio modo, calibrando il filamento prima della stampa; Quando si " +#~ "avvia una stampa multicolore/materiale, la stampante utilizzerà il " +#~ "parametro di compensazione predefinito per il filamento durante ogni " +#~ "cambio di filamento, che avrà un buon risultato nella maggior parte dei " +#~ "casi.\n" #~ "\n" #~ "Si prega di notare che ci sono alcuni casi che renderanno il risultato " #~ "della calibrazione non affidabile: utilizzo di una piastra di texture per " From 054a173af76e796ab6b6cfa57d59974d09cefcc7 Mon Sep 17 00:00:00 2001 From: yw4z Date: Mon, 18 May 2026 19:00:48 +0300 Subject: [PATCH 053/117] Fix possible crash on startup because of low max volumetric speed on profiles (#13716) * Update fdm_filament_common.json * update * Update fdm_filament_common.json --- resources/profiles/Afinia/filament/fdm_filament_common.json | 2 +- resources/profiles/Anycubic/filament/fdm_filament_common.json | 2 +- resources/profiles/Artillery/filament/fdm_filament_common.json | 2 +- resources/profiles/BBL/filament/fdm_filament_common.json | 2 +- resources/profiles/Blocks/filament/fdm_filament_common.json | 2 +- .../profiles/CONSTRUCT3D/filament/fdm_filament_common.json | 2 +- resources/profiles/CoLiDo/filament/fdm_filament_common.json | 2 +- resources/profiles/Comgrow/filament/fdm_filament_common.json | 2 +- resources/profiles/Creality/filament/fdm_filament_common.json | 2 +- resources/profiles/Cubicon/filament/fdm_filament_common.json | 2 +- resources/profiles/DeltaMaker/filament/fdm_filament_pet.json | 2 +- resources/profiles/DeltaMaker/filament/fdm_filament_pla.json | 2 +- resources/profiles/DeltaMaker/filament/fdm_filament_tpu.json | 2 +- resources/profiles/Dremel/filament/fdm_filament_common.json | 2 +- resources/profiles/Elegoo/filament/fdm_filament_common.json | 2 +- resources/profiles/Eryone/filament/fdm_filament_common.json | 2 +- resources/profiles/FLSun/filament/fdm_filament_common.json | 2 +- resources/profiles/Flashforge/filament/fdm_filament_common.json | 2 +- resources/profiles/Flashforge/filament/fdm_filament_pet.json | 2 +- resources/profiles/Flashforge/filament/fdm_filament_pla.json | 2 +- .../FlyingBear/filament/Ghost7/fdm_filament_common_Ghost7.json | 2 +- .../profiles/FlyingBear/filament/S1/fdm_filament_common_S1.json | 2 +- resources/profiles/FlyingBear/filament/fdm_filament_common.json | 2 +- .../InfiMech/filament/EX+APS/fdm_filament_common_EX+APS.json | 2 +- .../profiles/InfiMech/filament/EX/fdm_filament_common_EX.json | 2 +- .../profiles/InfiMech/filament/HSN/fdm_filament_common_HSN.json | 2 +- resources/profiles/InfiMech/filament/fdm_filament_common.json | 2 +- resources/profiles/LH/filament/fdm_filament_common.json | 2 +- resources/profiles/LONGER/filament/fdm_filament_common.json | 2 +- resources/profiles/OrcaArena/filament/fdm_filament_common.json | 2 +- .../OrcaFilamentLibrary/filament/base/fdm_filament_common.json | 2 +- resources/profiles/Peopoly/filament/fdm_filament_common.json | 2 +- resources/profiles/Phrozen/filament/fdm_filament_common.json | 2 +- resources/profiles/Prusa/filament/fdm_filament_common.json | 2 +- resources/profiles/Qidi/filament/fdm_filament_common.json | 2 +- resources/profiles/Ratrig/filament/fdm_filament_common.json | 2 +- resources/profiles/SecKit/filament/fdm_filament_common.json | 2 +- resources/profiles/Snapmaker/filament/fdm_filament_common.json | 2 +- resources/profiles/Tiertime/filament/fdm_filament_common.json | 2 +- resources/profiles/Vzbot/filament/fdm_filament_common.json | 2 +- .../profiles/Wanhao France/filament/fdm_filament_common.json | 2 +- .../profiles/WonderMaker/filament/fdm_filament_common.json | 2 +- resources/profiles/re3D/filament/fdm_filament_common.json | 2 +- 43 files changed, 43 insertions(+), 43 deletions(-) diff --git a/resources/profiles/Afinia/filament/fdm_filament_common.json b/resources/profiles/Afinia/filament/fdm_filament_common.json index dd89148ec9..94ed0997ee 100644 --- a/resources/profiles/Afinia/filament/fdm_filament_common.json +++ b/resources/profiles/Afinia/filament/fdm_filament_common.json @@ -61,7 +61,7 @@ "nil" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/Anycubic/filament/fdm_filament_common.json b/resources/profiles/Anycubic/filament/fdm_filament_common.json index 868a8418e6..2949245a6e 100644 --- a/resources/profiles/Anycubic/filament/fdm_filament_common.json +++ b/resources/profiles/Anycubic/filament/fdm_filament_common.json @@ -58,7 +58,7 @@ "1.75" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/Artillery/filament/fdm_filament_common.json b/resources/profiles/Artillery/filament/fdm_filament_common.json index f8aa522eb2..abf727c392 100644 --- a/resources/profiles/Artillery/filament/fdm_filament_common.json +++ b/resources/profiles/Artillery/filament/fdm_filament_common.json @@ -58,7 +58,7 @@ "1.75" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/BBL/filament/fdm_filament_common.json b/resources/profiles/BBL/filament/fdm_filament_common.json index 69fb92a4ec..caa037c74b 100644 --- a/resources/profiles/BBL/filament/fdm_filament_common.json +++ b/resources/profiles/BBL/filament/fdm_filament_common.json @@ -97,7 +97,7 @@ "nil" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_ramming_volumetric_speed": [ "-1" diff --git a/resources/profiles/Blocks/filament/fdm_filament_common.json b/resources/profiles/Blocks/filament/fdm_filament_common.json index 205c0d3075..9294e67f0f 100644 --- a/resources/profiles/Blocks/filament/fdm_filament_common.json +++ b/resources/profiles/Blocks/filament/fdm_filament_common.json @@ -64,7 +64,7 @@ "1.75" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/CONSTRUCT3D/filament/fdm_filament_common.json b/resources/profiles/CONSTRUCT3D/filament/fdm_filament_common.json index fc6dba4016..bf2a52192b 100644 --- a/resources/profiles/CONSTRUCT3D/filament/fdm_filament_common.json +++ b/resources/profiles/CONSTRUCT3D/filament/fdm_filament_common.json @@ -64,7 +64,7 @@ "1.75" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/CoLiDo/filament/fdm_filament_common.json b/resources/profiles/CoLiDo/filament/fdm_filament_common.json index 996fd8412b..ab6956a1a4 100644 --- a/resources/profiles/CoLiDo/filament/fdm_filament_common.json +++ b/resources/profiles/CoLiDo/filament/fdm_filament_common.json @@ -67,7 +67,7 @@ "1.75" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/Comgrow/filament/fdm_filament_common.json b/resources/profiles/Comgrow/filament/fdm_filament_common.json index 205c0d3075..9294e67f0f 100644 --- a/resources/profiles/Comgrow/filament/fdm_filament_common.json +++ b/resources/profiles/Comgrow/filament/fdm_filament_common.json @@ -64,7 +64,7 @@ "1.75" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/Creality/filament/fdm_filament_common.json b/resources/profiles/Creality/filament/fdm_filament_common.json index 205c0d3075..9294e67f0f 100644 --- a/resources/profiles/Creality/filament/fdm_filament_common.json +++ b/resources/profiles/Creality/filament/fdm_filament_common.json @@ -64,7 +64,7 @@ "1.75" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/Cubicon/filament/fdm_filament_common.json b/resources/profiles/Cubicon/filament/fdm_filament_common.json index 205c0d3075..9294e67f0f 100644 --- a/resources/profiles/Cubicon/filament/fdm_filament_common.json +++ b/resources/profiles/Cubicon/filament/fdm_filament_common.json @@ -64,7 +64,7 @@ "1.75" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/DeltaMaker/filament/fdm_filament_pet.json b/resources/profiles/DeltaMaker/filament/fdm_filament_pet.json index 6e60f86bbd..52cc522d49 100755 --- a/resources/profiles/DeltaMaker/filament/fdm_filament_pet.json +++ b/resources/profiles/DeltaMaker/filament/fdm_filament_pet.json @@ -14,7 +14,7 @@ "15" ], "filament_max_volumetric_speed": [ - "0" + "8" ], "filament_type": [ "PETG" diff --git a/resources/profiles/DeltaMaker/filament/fdm_filament_pla.json b/resources/profiles/DeltaMaker/filament/fdm_filament_pla.json index b4a6b48e02..6a791a5846 100755 --- a/resources/profiles/DeltaMaker/filament/fdm_filament_pla.json +++ b/resources/profiles/DeltaMaker/filament/fdm_filament_pla.json @@ -6,7 +6,7 @@ "instantiation": "false", "fan_cooling_layer_time": "100", "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_type": [ "PLA" diff --git a/resources/profiles/DeltaMaker/filament/fdm_filament_tpu.json b/resources/profiles/DeltaMaker/filament/fdm_filament_tpu.json index 5776cd8939..29b2e790a5 100755 --- a/resources/profiles/DeltaMaker/filament/fdm_filament_tpu.json +++ b/resources/profiles/DeltaMaker/filament/fdm_filament_tpu.json @@ -14,7 +14,7 @@ "30" ], "filament_max_volumetric_speed": [ - "0" + "3.6" ], "filament_type": [ "TPU" diff --git a/resources/profiles/Dremel/filament/fdm_filament_common.json b/resources/profiles/Dremel/filament/fdm_filament_common.json index fbfcbfb193..b87e860352 100644 --- a/resources/profiles/Dremel/filament/fdm_filament_common.json +++ b/resources/profiles/Dremel/filament/fdm_filament_common.json @@ -40,7 +40,7 @@ "1.75" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/Elegoo/filament/fdm_filament_common.json b/resources/profiles/Elegoo/filament/fdm_filament_common.json index 205c0d3075..9294e67f0f 100644 --- a/resources/profiles/Elegoo/filament/fdm_filament_common.json +++ b/resources/profiles/Elegoo/filament/fdm_filament_common.json @@ -64,7 +64,7 @@ "1.75" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/Eryone/filament/fdm_filament_common.json b/resources/profiles/Eryone/filament/fdm_filament_common.json index 83af95dd28..83f2a1f5d3 100644 --- a/resources/profiles/Eryone/filament/fdm_filament_common.json +++ b/resources/profiles/Eryone/filament/fdm_filament_common.json @@ -58,7 +58,7 @@ "1.75" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/FLSun/filament/fdm_filament_common.json b/resources/profiles/FLSun/filament/fdm_filament_common.json index 868a8418e6..2949245a6e 100644 --- a/resources/profiles/FLSun/filament/fdm_filament_common.json +++ b/resources/profiles/FLSun/filament/fdm_filament_common.json @@ -58,7 +58,7 @@ "1.75" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/Flashforge/filament/fdm_filament_common.json b/resources/profiles/Flashforge/filament/fdm_filament_common.json index cb6109169c..77353ae799 100644 --- a/resources/profiles/Flashforge/filament/fdm_filament_common.json +++ b/resources/profiles/Flashforge/filament/fdm_filament_common.json @@ -64,7 +64,7 @@ "1.75" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/Flashforge/filament/fdm_filament_pet.json b/resources/profiles/Flashforge/filament/fdm_filament_pet.json index f32eb3f72c..678ab77822 100644 --- a/resources/profiles/Flashforge/filament/fdm_filament_pet.json +++ b/resources/profiles/Flashforge/filament/fdm_filament_pet.json @@ -38,7 +38,7 @@ "15" ], "filament_max_volumetric_speed": [ - "0" + "8" ], "filament_type": [ "PETG" diff --git a/resources/profiles/Flashforge/filament/fdm_filament_pla.json b/resources/profiles/Flashforge/filament/fdm_filament_pla.json index 3086172aab..7dd7f4e42a 100644 --- a/resources/profiles/Flashforge/filament/fdm_filament_pla.json +++ b/resources/profiles/Flashforge/filament/fdm_filament_pla.json @@ -8,7 +8,7 @@ "100" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_type": [ "PLA" diff --git a/resources/profiles/FlyingBear/filament/Ghost7/fdm_filament_common_Ghost7.json b/resources/profiles/FlyingBear/filament/Ghost7/fdm_filament_common_Ghost7.json index 01c835d5c8..7200bb2a0f 100644 --- a/resources/profiles/FlyingBear/filament/Ghost7/fdm_filament_common_Ghost7.json +++ b/resources/profiles/FlyingBear/filament/Ghost7/fdm_filament_common_Ghost7.json @@ -64,7 +64,7 @@ "1.75" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/FlyingBear/filament/S1/fdm_filament_common_S1.json b/resources/profiles/FlyingBear/filament/S1/fdm_filament_common_S1.json index cefa33afa5..5501986fb5 100644 --- a/resources/profiles/FlyingBear/filament/S1/fdm_filament_common_S1.json +++ b/resources/profiles/FlyingBear/filament/S1/fdm_filament_common_S1.json @@ -64,7 +64,7 @@ "1.75" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/FlyingBear/filament/fdm_filament_common.json b/resources/profiles/FlyingBear/filament/fdm_filament_common.json index 64993e656a..b01bfa0377 100644 --- a/resources/profiles/FlyingBear/filament/fdm_filament_common.json +++ b/resources/profiles/FlyingBear/filament/fdm_filament_common.json @@ -64,7 +64,7 @@ "1.75" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/InfiMech/filament/EX+APS/fdm_filament_common_EX+APS.json b/resources/profiles/InfiMech/filament/EX+APS/fdm_filament_common_EX+APS.json index 9cc78b356c..2d051eeee2 100644 --- a/resources/profiles/InfiMech/filament/EX+APS/fdm_filament_common_EX+APS.json +++ b/resources/profiles/InfiMech/filament/EX+APS/fdm_filament_common_EX+APS.json @@ -64,7 +64,7 @@ "1.75" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/InfiMech/filament/EX/fdm_filament_common_EX.json b/resources/profiles/InfiMech/filament/EX/fdm_filament_common_EX.json index f4990a806c..a67718f5e0 100644 --- a/resources/profiles/InfiMech/filament/EX/fdm_filament_common_EX.json +++ b/resources/profiles/InfiMech/filament/EX/fdm_filament_common_EX.json @@ -64,7 +64,7 @@ "1.75" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/InfiMech/filament/HSN/fdm_filament_common_HSN.json b/resources/profiles/InfiMech/filament/HSN/fdm_filament_common_HSN.json index 847c3260e4..e8ae383cf3 100644 --- a/resources/profiles/InfiMech/filament/HSN/fdm_filament_common_HSN.json +++ b/resources/profiles/InfiMech/filament/HSN/fdm_filament_common_HSN.json @@ -64,7 +64,7 @@ "1.75" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/InfiMech/filament/fdm_filament_common.json b/resources/profiles/InfiMech/filament/fdm_filament_common.json index 64993e656a..b01bfa0377 100644 --- a/resources/profiles/InfiMech/filament/fdm_filament_common.json +++ b/resources/profiles/InfiMech/filament/fdm_filament_common.json @@ -64,7 +64,7 @@ "1.75" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/LH/filament/fdm_filament_common.json b/resources/profiles/LH/filament/fdm_filament_common.json index 788382bc9e..50f0df75c4 100644 --- a/resources/profiles/LH/filament/fdm_filament_common.json +++ b/resources/profiles/LH/filament/fdm_filament_common.json @@ -64,7 +64,7 @@ "1.75" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/LONGER/filament/fdm_filament_common.json b/resources/profiles/LONGER/filament/fdm_filament_common.json index 205c0d3075..9294e67f0f 100644 --- a/resources/profiles/LONGER/filament/fdm_filament_common.json +++ b/resources/profiles/LONGER/filament/fdm_filament_common.json @@ -64,7 +64,7 @@ "1.75" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/OrcaArena/filament/fdm_filament_common.json b/resources/profiles/OrcaArena/filament/fdm_filament_common.json index 4026949258..27b0ff7b5e 100644 --- a/resources/profiles/OrcaArena/filament/fdm_filament_common.json +++ b/resources/profiles/OrcaArena/filament/fdm_filament_common.json @@ -70,7 +70,7 @@ "1.75" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/OrcaFilamentLibrary/filament/base/fdm_filament_common.json b/resources/profiles/OrcaFilamentLibrary/filament/base/fdm_filament_common.json index 1dc6e50f39..ce95ca7c52 100644 --- a/resources/profiles/OrcaFilamentLibrary/filament/base/fdm_filament_common.json +++ b/resources/profiles/OrcaFilamentLibrary/filament/base/fdm_filament_common.json @@ -61,7 +61,7 @@ "nil" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/Peopoly/filament/fdm_filament_common.json b/resources/profiles/Peopoly/filament/fdm_filament_common.json index 868a8418e6..2949245a6e 100644 --- a/resources/profiles/Peopoly/filament/fdm_filament_common.json +++ b/resources/profiles/Peopoly/filament/fdm_filament_common.json @@ -58,7 +58,7 @@ "1.75" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/Phrozen/filament/fdm_filament_common.json b/resources/profiles/Phrozen/filament/fdm_filament_common.json index 868a8418e6..2949245a6e 100644 --- a/resources/profiles/Phrozen/filament/fdm_filament_common.json +++ b/resources/profiles/Phrozen/filament/fdm_filament_common.json @@ -58,7 +58,7 @@ "1.75" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/Prusa/filament/fdm_filament_common.json b/resources/profiles/Prusa/filament/fdm_filament_common.json index f8aa522eb2..abf727c392 100644 --- a/resources/profiles/Prusa/filament/fdm_filament_common.json +++ b/resources/profiles/Prusa/filament/fdm_filament_common.json @@ -58,7 +58,7 @@ "1.75" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/Qidi/filament/fdm_filament_common.json b/resources/profiles/Qidi/filament/fdm_filament_common.json index 81e3df4e5b..e0e07572c3 100644 --- a/resources/profiles/Qidi/filament/fdm_filament_common.json +++ b/resources/profiles/Qidi/filament/fdm_filament_common.json @@ -61,7 +61,7 @@ "1.75" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/Ratrig/filament/fdm_filament_common.json b/resources/profiles/Ratrig/filament/fdm_filament_common.json index 8d05a6f91a..ab5b8305a6 100644 --- a/resources/profiles/Ratrig/filament/fdm_filament_common.json +++ b/resources/profiles/Ratrig/filament/fdm_filament_common.json @@ -64,7 +64,7 @@ "1.75" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/SecKit/filament/fdm_filament_common.json b/resources/profiles/SecKit/filament/fdm_filament_common.json index 8d05a6f91a..ab5b8305a6 100644 --- a/resources/profiles/SecKit/filament/fdm_filament_common.json +++ b/resources/profiles/SecKit/filament/fdm_filament_common.json @@ -64,7 +64,7 @@ "1.75" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/Snapmaker/filament/fdm_filament_common.json b/resources/profiles/Snapmaker/filament/fdm_filament_common.json index 66296d8ded..04c0a64a6f 100644 --- a/resources/profiles/Snapmaker/filament/fdm_filament_common.json +++ b/resources/profiles/Snapmaker/filament/fdm_filament_common.json @@ -67,7 +67,7 @@ "1.75" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "0" diff --git a/resources/profiles/Tiertime/filament/fdm_filament_common.json b/resources/profiles/Tiertime/filament/fdm_filament_common.json index dd89148ec9..94ed0997ee 100644 --- a/resources/profiles/Tiertime/filament/fdm_filament_common.json +++ b/resources/profiles/Tiertime/filament/fdm_filament_common.json @@ -61,7 +61,7 @@ "nil" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/Vzbot/filament/fdm_filament_common.json b/resources/profiles/Vzbot/filament/fdm_filament_common.json index 205c0d3075..9294e67f0f 100644 --- a/resources/profiles/Vzbot/filament/fdm_filament_common.json +++ b/resources/profiles/Vzbot/filament/fdm_filament_common.json @@ -64,7 +64,7 @@ "1.75" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/Wanhao France/filament/fdm_filament_common.json b/resources/profiles/Wanhao France/filament/fdm_filament_common.json index 205c0d3075..9294e67f0f 100644 --- a/resources/profiles/Wanhao France/filament/fdm_filament_common.json +++ b/resources/profiles/Wanhao France/filament/fdm_filament_common.json @@ -64,7 +64,7 @@ "1.75" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/WonderMaker/filament/fdm_filament_common.json b/resources/profiles/WonderMaker/filament/fdm_filament_common.json index 458f1a0113..fff218a7f5 100755 --- a/resources/profiles/WonderMaker/filament/fdm_filament_common.json +++ b/resources/profiles/WonderMaker/filament/fdm_filament_common.json @@ -61,7 +61,7 @@ "nil" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" diff --git a/resources/profiles/re3D/filament/fdm_filament_common.json b/resources/profiles/re3D/filament/fdm_filament_common.json index 3fbf7a0288..745f258139 100644 --- a/resources/profiles/re3D/filament/fdm_filament_common.json +++ b/resources/profiles/re3D/filament/fdm_filament_common.json @@ -64,7 +64,7 @@ "2.85" ], "filament_max_volumetric_speed": [ - "0" + "12" ], "filament_minimal_purge_on_wipe_tower": [ "15" From dc12126b78104fb339cff943b664a2f6cca09794 Mon Sep 17 00:00:00 2001 From: yw4z Date: Mon, 18 May 2026 19:01:42 +0300 Subject: [PATCH 054/117] Fix 2 Linux assertation errors on gtk_window_resize() (#13718) Update DropDown.cpp --- src/slic3r/GUI/Widgets/DropDown.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Widgets/DropDown.cpp b/src/slic3r/GUI/Widgets/DropDown.cpp index abbecc4353..f33fd04800 100644 --- a/src/slic3r/GUI/Widgets/DropDown.cpp +++ b/src/slic3r/GUI/Widgets/DropDown.cpp @@ -554,7 +554,8 @@ void DropDown::messureSize() wxWindow::SetSize(szContent); #ifdef __WXGTK__ // Gtk has a wrapper window for popup widget - gtk_window_resize (GTK_WINDOW (m_widget), szContent.x, szContent.y); + if (szContent.x > 0 && szContent.y > 0) + gtk_window_resize (GTK_WINDOW (m_widget), szContent.x, szContent.y); #endif if (!groups.empty() && subDropDown == nullptr) { subDropDown = new DropDown(items); From 88b4a63228d21edc6685c56904a317bfa32ace28 Mon Sep 17 00:00:00 2001 From: SoftFever Date: Tue, 19 May 2026 01:01:53 +0800 Subject: [PATCH 055/117] Fix Linux data-view dropdown popup parenting (#13721) Delay opening the object-list filament dropdown on wxGTK until the editor window is mapped, avoiding popup creation without a valid native toplevel. Also set the GTK transient parent for dropdown popups created from data-view cell editors. --- src/slic3r/GUI/ExtraRenderers.cpp | 12 ++++++++++-- src/slic3r/GUI/Widgets/DropDown.cpp | 24 ++++++++++++++++++++++++ src/slic3r/GUI/Widgets/DropDown.hpp | 2 ++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/ExtraRenderers.cpp b/src/slic3r/GUI/ExtraRenderers.cpp index 28837dbdad..18811ef241 100644 --- a/src/slic3r/GUI/ExtraRenderers.cpp +++ b/src/slic3r/GUI/ExtraRenderers.cpp @@ -324,9 +324,18 @@ wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelR else c_editor->SetSelection(atoi(data.GetText().c_str()) - 1); - // Open the dropdown immediately when the editor is focused. c_editor->Bind(wxEVT_SET_FOCUS, [c_editor](wxFocusEvent& evt) { +#ifdef __WXGTK__ + // On wxGTK the data-view editor may receive focus before its native + // window is mapped. Opening the popup one event later avoids creating + // the GTK popup without a valid toplevel parent. + c_editor->CallAfter([c_editor]() { + if (c_editor->IsShownOnScreen()) + c_editor->ForceDropdownOpen(); + }); +#else c_editor->ForceDropdownOpen(); +#endif evt.Skip(); }); @@ -392,4 +401,3 @@ wxSize TextRenderer::GetSize() const return GetTextExtent(m_value); } - diff --git a/src/slic3r/GUI/Widgets/DropDown.cpp b/src/slic3r/GUI/Widgets/DropDown.cpp index f33fd04800..973113d0ac 100644 --- a/src/slic3r/GUI/Widgets/DropDown.cpp +++ b/src/slic3r/GUI/Widgets/DropDown.cpp @@ -167,6 +167,30 @@ bool DropDown::HasDismissLongTime() (now - dismissTime).total_milliseconds() >= 20; } +void DropDown::Popup(wxWindow *focus) +{ +#ifdef __WXGTK__ + if (!mainDropDown && m_widget) { + // Data-view cell editors can receive focus before wxGTK infers a + // native popup parent, so provide the current toplevel explicitly. + GtkWindow *transient_parent = nullptr; + for (wxWindow *win = GetParent(); win; win = win->GetParent()) { + GtkWidget *widget = static_cast(win->GetHandle()); + if (!widget) + continue; + GtkWidget *top = gtk_widget_get_toplevel(widget); + if (GTK_IS_WINDOW(top)) { + transient_parent = GTK_WINDOW(top); + break; + } + } + if (transient_parent) + gtk_window_set_transient_for(GTK_WINDOW(m_widget), transient_parent); + } +#endif + PopupWindow::Popup(focus); +} + void DropDown::paintEvent(wxPaintEvent& evt) { // depending on your system you may need to look at double-buffered dcs diff --git a/src/slic3r/GUI/Widgets/DropDown.hpp b/src/slic3r/GUI/Widgets/DropDown.hpp index 639a13e526..09041e3dc0 100644 --- a/src/slic3r/GUI/Widgets/DropDown.hpp +++ b/src/slic3r/GUI/Widgets/DropDown.hpp @@ -105,6 +105,8 @@ public: bool HasDismissLongTime(); + void Popup(wxWindow *focus = nullptr) override; + protected: void Dismiss() override; From d8369e5f75d2df6d336a933cea8439c72581284a Mon Sep 17 00:00:00 2001 From: SoftFever Date: Tue, 19 May 2026 01:38:37 +0800 Subject: [PATCH 056/117] Fix faint toolbar icons on Wayland (#13723) Use glBlendFuncSeparate in GLTexture::render_sub_texture so destination alpha stays at 1.0. The Wayland compositor honors framebuffer alpha for window compositing; the previous straight-alpha blend reduced dst alpha at anti-aliased icon edges, making them semi-transparent against the desktop. RGB blending is unchanged, so X11/Windows/macOS are unaffected. --- src/slic3r/GUI/GLTexture.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index 7bf4d008d4..d670181b1b 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -663,7 +663,9 @@ void GLTexture::render_texture(unsigned int tex_id, float left, float right, flo void GLTexture::render_sub_texture(unsigned int tex_id, float left, float right, float bottom, float top, const GLTexture::Quad_UVs& uvs) { glsafe(::glEnable(GL_BLEND)); - glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + // Orca: fix washed-out toolbar icons on Wayland: keep destination alpha at 1.0 so the compositor + // does not treat anti-aliased icon edges as window transparency. + glsafe(::glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); glsafe(::glEnable(GL_TEXTURE_2D)); glsafe(::glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE)); From f78836bef40f182e4ca748d381240fd604bc03a8 Mon Sep 17 00:00:00 2001 From: Andrew <159703254+andrewsoonqn@users.noreply.github.com> Date: Tue, 19 May 2026 11:20:04 +0800 Subject: [PATCH 057/117] Stop tracking .idea files (#13735) --- .idea/.gitignore | 8 -------- .idea/OrcaSlicer.iml | 2 -- .idea/codeStyles/Project.xml | 7 ------- .idea/codeStyles/codeStyleConfig.xml | 5 ----- .idea/misc.xml | 4 ---- .idea/modules.xml | 8 -------- .idea/vcs.xml | 6 ------ 7 files changed, 40 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/OrcaSlicer.iml delete mode 100644 .idea/codeStyles/Project.xml delete mode 100644 .idea/codeStyles/codeStyleConfig.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b81b0..0000000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/OrcaSlicer.iml b/.idea/OrcaSlicer.iml deleted file mode 100644 index f08604bb65..0000000000 --- a/.idea/OrcaSlicer.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml deleted file mode 100644 index f60388162d..0000000000 --- a/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index 79ee123c2b..0000000000 --- a/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 79b3c94830..0000000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index b80725f1d9..0000000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1ddfbb..0000000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From d0fbda3af1ce25597507c9f4f9ce3060191ad4d1 Mon Sep 17 00:00:00 2001 From: SoftFever Date: Tue, 19 May 2026 14:26:34 +0800 Subject: [PATCH 058/117] Fix Linux segfault when loading Fluidd/Mainsail printer hosts (#7210) (#13724) * Fix Linux segfault when loading Fluidd/Mainsail printer hosts (#7210) Co-authored-by: VittC <58783944+VittC@users.noreply.github.com> * narrow down the changes --------- Co-authored-by: VittC <58783944+VittC@users.noreply.github.com> --- src/slic3r/GUI/PrinterWebView.cpp | 91 +++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/src/slic3r/GUI/PrinterWebView.cpp b/src/slic3r/GUI/PrinterWebView.cpp index 1dccfab66f..c4c5cbcc5f 100644 --- a/src/slic3r/GUI/PrinterWebView.cpp +++ b/src/slic3r/GUI/PrinterWebView.cpp @@ -23,6 +23,91 @@ namespace Slic3r { namespace GUI { +#ifdef __linux__ +// Workaround for crash in WebKitGTK when loading Fluidd < v1.37.0 or Mainsail < v2.16.1. +// Their bundled vue-resize component detects container resizes by inserting +//